我在表上有一个延迟AFTER UPDATE
触发器,设置为在某个列更新时触发。这是我用作计数器的整数类型。
我不是100%肯定,但看起来如果我在交易期间将该特定列增加100次,则触发器会在事务结束时排队并执行100次。
我希望触发器每行只安排一次,无论我增加了多少次该列。
我能以某种方式这样做吗? 或者,如果触发的触发器必须排队,无论它们是否重复,我是否可以在首次运行触发器时清除此队列?
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 :(得分:12)
这是一个棘手的问题。但可以使用 PostgreSQL 9.0 中引入的per-column triggers and conditional trigger execution来完成。
此解决方案需要每行“更新”标记。为简单起见,请在同一个表中使用boolean
列。但它可以在另一个表中,甚至可以在每个事务中使用临时表。
昂贵的有效负载执行每行一次,其中计数器更新(一次或多次)。
这也应该执行,因为......
考虑以下
使用单独的模式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.)重置“已更新”标志。
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
分别看到延迟效应。如果一起执行(在一个事务中),SELECT将显示新的tbl.counter
,但旧的tbl2.trig_exec_count
。
UPDATE x.tbl SET counter = counter + 1;
SELECT * FROM x.tbl;
现在,多次更新计数器(在一个事务中)。有效负载只会执行一次。的瞧!强>
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;
答案 1 :(得分:7)
我不知道如何将触发器执行折叠为每个事务的每个(更新的)行一次,但您可以使用TEMPORARY ON COMMIT DROP表来模拟这个,该表跟踪这些已修改的行并仅执行昂贵的操作每行每次一次:
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;
当然存在与该临时表名称冲突的风险,因此请谨慎选择。