我有一个触发器,可以在表插入或更新时执行一个函数。它看起来像这样:
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();
当我插入一行时,它似乎运行得非常慢。使用说明分析我确定这个触发器需要将近400毫秒才能完成。
Result (cost=0.00..0.03 rows=1 width=0) (actual time=0.026..0.029 rows=1 loops=1)
Trigger for constraint location_fkey_tr_by: time=0.063 calls=1
Trigger trigger_fk_location_area: time=361.878 calls=1
Trigger trigger_update_objects_location: time=355.033 calls=1
Total runtime: 717.229 ms
(5 rows)
但是,如果我在函数中运行两批SQL,它们每个只运行3或4毫秒!
第一部分:
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)
第二部分:
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)
这对我没有意义。
有什么想法吗?
感谢。
答案 0 :(得分:1)
您正在设置触发器以针对每一行运行,然后在触发器功能内部,您在整个表上执行另一个选择。做一个或另一个。 (尝试将FOR EACH ROW改为FOR EACH STATEMENT。)
答案 1 :(得分:1)
如果为函数准备了查询,postgres有时可能会创建不同的计划。如果我将函数更改为实际执行SQL,那么它每次都会创建一个新计划,并且它对我的特定场景运行得更快(奇怪的是!)
这基本上解决了我的问题:
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();