Postgres 触发器执行速度有差异吗?

发布于 2024-09-27 15:03:30 字数 4371 浏览 0 评论 0原文

我有一个触发器,可以在表插入或更新时执行函数。它看起来像这样:

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)

这对我来说毫无意义。

有什么想法吗?

谢谢。

I have a trigger that executes a function on table insert or update. It looks like this:

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();

When I insert a row, it seems to run very slowly. Using explain analyze I determined that this trigger was taking nearly 400ms to complete.

 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)

However, if I run the two lots of SQL in the function, they each only take 3 or 4ms to run!

FIRST PART:

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)

SECOND PART:

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)

This makes no sense to me.

Any thoughts?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

滥情稳全场 2024-10-04 15:03:30

您将触发器设置为针对每一行运行,然后在触发器函数内对整个表进行另一个选择。做其中之一或另一个。 (尝试将 FOR EACH ROW 更改为 FOR EACH STATEMENT。)

You're setting up the trigger to run for each row, and then inside the trigger function you're doing another select on the whole table. Do one or the other. (Try changing FOR EACH ROW to FOR EACH STATEMENT.)

起风了 2024-10-04 15:03:30

如果查询已经为该函数准备好了,那么 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();

It looks like postgres may sometimes create a different plan if the query has been prepared for the function. If I change the function to actually execute the SQL then it creates a new plan every time and it does operate much faster for my particular scenario (strangely!)

This basically solves my problem:

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();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文