2010-10-14 4 views
1

J'ai un déclencheur qui exécute une fonction sur l'insertion ou la mise à jour de la table. Cela ressemble à ceci:Différence de Postgres dans la vitesse d'exécution du déclencheur?

CREATE OR REPLACE FUNCTION func_fk_location_area() 
RETURNS "trigger" AS $$ 
BEGIN 
    IF EXISTS (
     -- there was a row valid in area when location started 
     SELECT * FROM location 
     WHERE NOT EXISTS (
      SELECT * FROM area 
      WHERE area.key=location.key 
       AND area.id=location.area_id 
       AND ( (area.tr_from<=location.tr_from AND area.tr_until>location.tr_from) OR 
         (area.tr_from=location.tr_from AND area.tr_until=location.tr_from))) 
    ) OR EXISTS (
     -- there was a row valid in area when location ended 
     SELECT * FROM location 
     WHERE NOT EXISTS (
      SELECT * FROM area 
      WHERE area.key=location.key 
       AND area.id=location.area_id 
       AND ( (area.tr_from<location.tr_until AND area.tr_until>=location.tr_until) OR 
         (area.tr_from=location.tr_until AND area.tr_until=location.tr_until))) 
    ) 
    THEN 
     RAISE EXCEPTION 'FK location_area integrity violation.'; 
    END IF; 
    RETURN NEW; 
END; 
$$ LANGUAGE plpgsql; 
CREATE TRIGGER trigger_fk_area_location AFTER DELETE OR UPDATE ON area 
    FOR EACH ROW EXECUTE PROCEDURE func_fk_location_area(); 
CREATE TRIGGER trigger_fk_location_area AFTER INSERT OR UPDATE ON location 
    FOR EACH ROW EXECUTE PROCEDURE func_fk_location_area(); 

Lorsque j'insère une ligne, elle semble fonctionner très lentement. En utilisant l'analyse d'explication, j'ai déterminé que ce déclencheur prenait près de 400 ms à compléter. Cependant, si j'exécute les deux lots de SQL dans la fonction, ils ne prennent chacun que 3 ou 4ms pour s'exécuter!

PREMIERE PARTIE:

mydb=# explain analyze 
mydb-#    SELECT * FROM location 
mydb-#    WHERE NOT EXISTS (
mydb(#     SELECT * FROM area 
mydb(#     WHERE area.key=location.key 
mydb(#     AND area.id=location.area_id 
mydb(#     AND ( (area.tr_from<location.tr_until AND area.tr_until>=location.tr_until) OR 
mydb(#       (area.tr_from=location.tr_until AND area.tr_until=location.tr_until))); 

Hash Anti Join (cost=14.68..146.84 rows=1754 width=126) (actual time=5.512..5.512 rows=0 loops=1) 
    Hash Cond: ((location.key = area.key) AND (location.area_id = area.id)) 
    Join Filter: (((area.tr_from < location.tr_until) AND (area.tr_until >= location.tr_until)) OR ((area.tr_from = location.tr_until) AND (area.tr_until = locat 
ion.tr_until))) 
    -> Seq Scan on location (cost=0.00..79.91 rows=2391 width=126) (actual time=0.005..1.016 rows=2393 loops=1) 
    -> Hash (cost=8.87..8.87 rows=387 width=37) (actual time=0.497..0.497 rows=387 loops=1) 
     -> Seq Scan on area (cost=0.00..8.87 rows=387 width=37) (actual time=0.004..0.250 rows=387 loops=1) 
Total runtime: 5.562 ms 
(7 rows) 

deuxième partie:

mydb=# explain analyze 
mydb-#    SELECT * FROM location 
mydb-#    WHERE NOT EXISTS (
mydb(#     SELECT * FROM area 
mydb(#     WHERE area.key=location.key 
mydb(#     AND area.id=location.area_id 
mydb(#     AND ( (area.tr_from<location.tr_until AND area.tr_until>=location.tr_until) OR 
mydb(#       (area.tr_from=location.tr_until AND area.tr_until=location.tr_until))); 

Hash Anti Join (cost=14.68..146.84 rows=1754 width=126) (actual time=5.666..5.666 rows=0 loops=1) 
    Hash Cond: ((location.key = area.key) AND (location.area_id = area.id)) 
    Join Filter: (((area.tr_from < location.tr_until) AND (area.tr_until >= location.tr_until)) OR ((area.tr_from = location.tr_until) AND (area.tr_until = locat 
ion.tr_until))) 
    -> Seq Scan on location (cost=0.00..79.91 rows=2391 width=126) (actual time=0.005..1.072 rows=2393 loops=1) 
    -> Hash (cost=8.87..8.87 rows=387 width=37) (actual time=0.509..0.509 rows=387 loops=1) 
     -> Seq Scan on area (cost=0.00..8.87 rows=387 width=37) (actual time=0.007..0.239 rows=387 loops=1) 
Total runtime: 5.725 ms 
(7 rows) 

Cela fait aucun sens pour moi.

Des pensées?

Merci.

+0

Vous n'utilisez pas de clés étrangères, mais émulez un fk avec un trigger trigger? Si oui, pourquoi? – Kuberchaun

+0

Pour une bonne habitude, changez votre * en quelque chose comme 'X'. Nous marquons tous les * dans les révisions de code et ne laissons pas passer cette révision même si la performance n'est pas affectée. – Kuberchaun

+0

Pouvons-nous voir votre code avec POUR CHAQUE DÉCLARATION et l'expliquer? – Kuberchaun

Répondre

1

Il semble que postgres peut parfois créer un plan différent si la requête a été préparée pour la fonction. Si je change la fonction pour exécuter l'SQL il crée un nouveau plan chaque fois et il ne fonctionne beaucoup plus rapide pour mon scénario particulier

Cela résout fondamentalement mon problème (étrangement!):

CREATE OR REPLACE FUNCTION func_fk_location_area() 
RETURNS "trigger" AS $$ 
DECLARE 
    myst TEXT; 
    mysv TEXT; 
    myrec RECORD; 
BEGIN 
    myst := 'SELECT id FROM location WHERE NOT EXISTS (SELECT id FROM area WHERE area.key=location.key AND area.id=location.area_id '; 
    mysv := 'AND ((area.tr_from<=location.tr_from AND area.tr_until>location.tr_from) OR (area.tr_from=location.tr_from AND area.tr_until=location.tr_from)))'; 
    EXECUTE myst || mysv; 
    IF FOUND THEN 
     RAISE EXCEPTION 'FK location_area integrity violation.'; 
     RETURN NULL; 
    END IF; 
    mysv := 'AND ((area.tr_from<location.tr_until AND area.tr_until>=location.tr_until) OR (area.tr_from=location.tr_until AND area.tr_until=location.tr_until)))'; 
    EXECUTE myst || mysv; 
    IF FOUND THEN 
     RAISE EXCEPTION 'FK location_area integrity violation.'; 
    END IF; 
    RETURN NULL; 
END; 
$$ LANGUAGE plpgsql; 
CREATE TRIGGER trigger_fk_area_location AFTER DELETE OR UPDATE ON area 
    FOR EACH ROW EXECUTE PROCEDURE func_fk_location_area(); 
CREATE TRIGGER trigger_fk_location_area AFTER INSERT OR UPDATE ON location 
    FOR EACH ROW EXECUTE PROCEDURE func_fk_location_area(); 
1

Vous définissez le déclencheur à exécuter pour chaque ligne, puis à l'intérieur de la fonction de déclenchement, vous effectuez une autre sélection sur l'ensemble de la table. Fais l'un ou l'autre. (Essayez de changer FOR EACH ROW pour chaque déclaration.)

+0

Essayé cela, aucune différence :-( – Mark

+0

Hmm. Eh bien, avez-vous regardé le plan de requête lorsque vous ou les deux requêtes ensemble? Pouvez-vous combiner les deux requêtes en déplaçant la logique dans une seule clause WHERE? – Pointy

+0

Pas encore - ce que je pourrais essayer est d'essayer la fonction avec l'une des requêtes manquantes au cas où il ferait une sorte d'écrasement impair des 2 requêtes.Je rapporterai .. – Mark

Questions connexes