1

テーブルの挿入または更新時に関数を実行するトリガーがあります。次のようになります。

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

行を挿入すると、実行が非常に遅くなるようです。Explain Analyst を使用して、このトリガーが完了するまでに 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)

ただし、関数で 2 つの多くの 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)

これは私には意味がありません。

何かご意見は?

ありがとう。

4

2 に答える 2

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();
于 2010-11-12T10:13:09.633 に答える
1

行ごとにトリガーを実行するように設定してから、トリガー関数内でテーブル全体に対して別の選択を行っています。どちらかを行います。(FOR EACH ROW を FOR EACH STATEMENT に変更してみてください。)

于 2010-10-14T14:33:41.770 に答える