2012-01-20 68 views
9

テーブルに遅延AFTER UPDATEトリガがあり、特定のカラムが更新されたときに起動するように設定されています。これは私がカウンタとして使用している整数型です。PostgreSQLでローごとに1回だけ遅延トリガを実行

私は100%確実ではありませんが、トランザクション中に特定の列を100回インクリメントすると、トリガーはキューに入れられ、トランザクションの最後に100回実行されたようです。

トリガーは、その列を何回増分しても行ごとに1回しかスケジュールされません。

何とかできますか? トリガされたトリガが重複しているかどうかに関係なくキューに入れなければならない場合は、トリガの最初の実行中にこのキューをクリアすることはできますか?

Postgresのバージョンは9.1です。ここで私が得たものです:

CREATE CONSTRAINT TRIGGER counter_change 
    AFTER UPDATE OF "Counter" ON "table" 
    DEFERRABLE INITIALLY DEFERRED 
    FOR EACH ROW 
    EXECUTE PROCEDURE counter_change(); 

CREATE OR REPLACE FUNCTION counter_change() 
    RETURNS trigger 
    LANGUAGE plpgsql 
    AS $$ 
DECLARE 
BEGIN 

PERFORM some_expensive_procedure(NEW."id"); 

RETURN NEW; 

END;$$; 
+0

ご使用のバージョンのPostgresが役立ちます。トリガーとトリガー機能の(基本)コード。 –

答えて

11

これは難しい問題です。しかし、PostgreSQL 9.0で導入されたper-column triggers and conditional trigger executionで行うことができます。

この解決方法では、の行ごとに"updated"フラグが必要です。単純化のため、同じ表のboolean列を使用してください。しかし、トランザクションごとに別のテーブルに格納することも、一時テーブルに格納することもできます。

高価なペイロードは、に1回、が実行され、カウンタが更新されます(1回または複数回)。

この...

  • ...それは
  • (うまくスケール)ルートでのトリガーの複数の呼び出しを避けるためにも、うまくを実行する必要があります...は、(追加の行は変更されません。テーブルの膨らみを最小限に抑えます)
  • ...高価な例外処理は必要ありません。

は、テスト環境として別のスキーマxではPostgreSQL 9.1でテストされ、次の

デモ

を考えてみましょう。

テーブルとダミー行

-- DROP SCHEMA x; 
CREATE SCHEMA x; 

CREATE TABLE x.tbl (
id int 
,counter int 
,trig_exec_count integer -- for monitoring payload execution. 
,updated bool); 

を挿入し、それが複数の行で動作実証する二列:

INSERT INTO x.tbl VALUES 
(1, 0, 0, NULL) 
,(2, 0, 0, NULL); 

トリガ関数とトリガ

1)を実行し、高価なペイロード

CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_1() 
    RETURNS trigger AS 
$BODY$ 
BEGIN 

-- PERFORM some_expensive_procedure(NEW.id); 
-- Update trig_exec_count to count execution of expensive payload. 
-- Could be in another table, for simplicity, I use the same: 

UPDATE x.tbl t 
SET trig_exec_count = trig_exec_count + 1 
WHERE t.id = NEW.id; 

RETURN NULL; -- RETURN value of AFTER trigger is ignored anyway 

END; 
$BODY$ LANGUAGE plpgsql; 

2.)行を更新済みとしてフラグを立てます。

CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_2() 
    RETURNS trigger AS 
$BODY$ 
BEGIN 

UPDATE x.tbl 
SET updated = TRUE 
WHERE id = NEW.id; 
RETURN NULL; 

END; 
$BODY$ LANGUAGE plpgsql; 

3.) "updated"フラグをリセットします。

CREATE OR REPLACE FUNCTION x.trg_upaft_counter_change_3() 
    RETURNS trigger AS 
$BODY$ 
BEGIN 

UPDATE x.tbl 
SET updated = NULL 
WHERE id = NEW.id; 
RETURN NULL; 

END; 
$BODY$ LANGUAGE plpgsql; 

トリガー名が該当します。アルファベット順に実行される同じイベントのために呼び出されます。

1)ペイロード、まだ "更新" ではない場合にのみ:まだ "更新" ではない場合にのみ、更新されたとして

CREATE CONSTRAINT TRIGGER upaft_counter_change_1 
    AFTER UPDATE OF counter ON x.tbl 
    DEFERRABLE INITIALLY DEFERRED 
    FOR EACH ROW 
    WHEN (NEW.updated IS NULL) 
    EXECUTE PROCEDURE x.trg_upaft_counter_change_1(); 

2)フラグ行:

CREATE TRIGGER upaft_counter_change_2 -- not deferred! 
    AFTER UPDATE OF counter ON x.tbl 
    FOR EACH ROW 
    WHEN (NEW.updated IS NULL) 
    EXECUTE PROCEDURE x.trg_upaft_counter_change_2(); 

3)リセットフラグ。トリガ条件のため無限ループはありません。別途

CREATE CONSTRAINT TRIGGER upaft_counter_change_3 
    AFTER UPDATE OF updated ON x.tbl 
    DEFERRABLE INITIALLY DEFERRED 
    FOR EACH ROW 
    WHEN (NEW.updated)     -- 
    EXECUTE PROCEDURE x.trg_upaft_counter_change_3(); 

テスト

実行UPDATE & SELECT繰延効果を確認します。 (1つのトランザクションで)一緒に実行されると、SELECTには新しいtbl.counterが表示されますが、古いtbl2.trig_exec_countが表示されます。

UPDATE x.tbl SET counter = counter + 1; 

SELECT * FROM x.tbl; 

ここで、(1つのトランザクションで)カウンタを複数回更新します。ペイロードは一度だけ実行されます。 Voilá!

UPDATE x.tbl SET counter = counter + 1; 
UPDATE x.tbl SET counter = counter + 1; 
UPDATE x.tbl SET counter = counter + 1; 
UPDATE x.tbl SET counter = counter + 1; 
UPDATE x.tbl SET counter = counter + 1; 

SELECT * FROM x.tbl; 
+0

+1興味深いアプローチ – pilcrow

+0

私はこのソリューションが好きです、あなたの努力に感謝します。 – jjames

+2

私が知っているパーティーに少し遅れますが、ペイロード機能を1回だけ実行し、* last *を実行することは可能です(最後のUPDATE stmtの場合と同じです)。 – andreak

6

私はトランザクションごとに一回(更新)行あたりのトリガの実行を崩壊させる方法を知りませんが、あなたはTEMPORARY ONでこれをエミュレートすることができ修飾されたものを追跡DROPテーブルをCOMMIT行とTXごとに一度だけの行あたりの高価な操作を実行します。

CREATE OR REPLACE FUNCTION counter_change() RETURNS TRIGGER 
AS $$ 
BEGIN 
    -- If we're the first invocation of this trigger in this tx, 
    -- make our scratch table. Create unique index separately to 
    -- suppress avoid NOTICEs without fiddling with log_min_messages 
    BEGIN 
    CREATE LOCAL TEMPORARY TABLE tbl_counter_tx_once 
     ("id" AS_APPROPRIATE NOT NULL) 
     ON COMMIT DROP; 
    CREATE UNIQUE INDEX ON tbl_counter_tx_once AS ("id"); 
    EXCEPTION WHEN duplicate_table THEN 
    NULL; 
    END; 

    -- If we're the first invocation in this tx *for this row*, 
    -- then do our expensive operation. 
    BEGIN 
    INSERT INTO tbl_counter_tx_once ("id") VALUES (NEW."id"); 
    PERFORM SOME_EXPENSIVE_OPERATION_HERE(NEW."id"); 
    EXCEPTION WHEN unique_violation THEN 
    NULL; 
    END; 

    RETURN NEW; 
END; 
$$ LANGUAGE plpgsql; 

あり、その一時テーブルと名前の衝突の危険性はもちろんですが、とても慎重に選択します。

+0

例外処理は高価で、不要です。 ['CREATE TABLE IF NOT EXISTS'](http://www.postgresql.org/docs/current/interactive/sql-createtable.html)(9.1の新機能)と' IF NOT EXISTS(SELECT ..)THEN。 ..; INSERT INTO tbl ..; END IF; '。また、 'LOCAL'はPostgreSQLのノイズワードです。 –

+0

Re:非例外ベースの処理、はい、それを行う方法は複数あります。 (実際に私の最初にテストされた解決策は存在しない場合に作成されました。)Re:ローカル、はい、私は知っています、そして、ここで私はそれがこのテーブルの使用の目的を補強すると思う。 – pilcrow

+1

+1あなたの解決策がupvoteにもふさわしいことは別として、高度なもの。現在のトランザクションIDを['txid_current()'](http://www.postgresql.org/docs/current/interactive/functions-info.html#FUNCTIONS-TXID-SNAPSHOT)とともに一時的な名前に含めることができます表。ただし、EXECUTEで動的SQLを使用するように強制します。 ** OR **、より良いのは、静的SQLで問題を回避できる、安定した名前のテンポラリテーブルに列 'xid'を追加する方が良いでしょう! –

関連する問題