在PostgreSQL中,每行仅执行一次延迟触发器

在PostgreSQL中,每行仅执行一次延迟触发器,postgresql,database-design,triggers,plpgsql,Postgresql,Database Design,Triggers,Plpgsql,我在表上有一个延迟更新后触发器,设置为在某个列更新时触发。这是一个整数类型,我用它作为计数器 我不是100%确定,但如果在事务期间将特定列增加100次,触发器将排队并在事务结束时执行100次 我希望触发器每行只调度一次,无论我将该列递增多少次 我能做到吗? 或者,如果触发的触发器必须排队,无论它们是否重复,我是否可以在第一次运行触发器时清除此队列 Postgres的版本是9.1。以下是我得到的: CREATE CONSTRAINT TRIGGER counter_change AFTER

我在表上有一个延迟更新后触发器,设置为在某个列更新时触发。这是一个整数类型,我用它作为计数器

我不是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;$$;

我不知道有什么方法可以将触发器执行折叠为每事务每更新一行执行一次,但您可以使用一个临时提交删除表来模拟这一点,该表跟踪这些修改的行,并且每发送一行只执行一次昂贵的操作:

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;

当然,这个临时表存在名称冲突的风险,所以要明智地选择。

这是一个棘手的问题。但这可以通过PostgreSQL 9.0中引入的实现

对于此解决方案,每行需要一个更新的标志。为了简单起见,在同一个表中使用布尔列。但它可以在另一个表中,甚至每个事务都可以在一个临时表中

昂贵的有效负载每行执行一次,其中计数器更新一次或多次

这也应该表现良好,因为

... 它很好地避免了在根级别多次调用触发器 ... 不更改其他行最小化表膨胀 ... 不需要昂贵的异常处理。 考虑以下几点

演示 在PostgreSQL 9.1中使用单独的模式x作为测试环境进行测试

表和虚拟行 插入两行以演示它可用于多行:

INSERT INTO x.tbl VALUES
 (1, 0, 0, NULL)
,(2, 0, 0, NULL);
触发器函数和触发器 一,。执行昂贵的负载

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;
二,。将行标记为已更新

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;
三,。重置更新的标志

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;
触发器名称是相关的!为同一事件调用,它们按字母顺序执行

一,。有效载荷,仅当尚未更新时:

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();
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();
二,。仅当尚未更新时,才将行标记为已更新:

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();
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();
三,。重置标志。由于触发条件,没有无止境的循环

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();
测验 单独运行更新并选择以查看延迟效果。如果在一个事务中同时执行,SELECT将显示新的tbl.counter,但显示旧的tbl2.trig\u exec\u计数

现在,在一个事务中多次更新计数器。有效负载将只执行一次。瞧


你的Postgres版本会有所帮助。也是触发器和触发器函数的基本代码。异常处理代价高昂且不需要。考虑新的9.1,如果不存在选择…然后插入tbl。。;结束如果;。此外,在PostgreSQL中,本地化只是一个干扰词。Re:基于非异常的处理,是的,有多种方法可以做到这一点。事实上,我的第一个测试解决方案是创建(如果不存在的话)。Re:LOCAL,是的,我知道,在这里我认为它加强了使用此表的目的。+1除此之外,您的解决方案也值得投票表决。先进的东西。您可以在临时表的名称中包含当前事务ID。但是,这会迫使您将动态SQL与EXECUTE一起使用。或者,更好的方法是,向临时表中添加一个具有稳定名称的列xid,这样就可以避免静态SQL的问题!对于新的表、索引和异常处理来说,这似乎有点沉重,目前我的处理速度是每秒200个事务,而且还在上升。我还需要它在事务中的所有工作完成后运行,而不是在该特定表上的第一次插入之后运行。Re:看起来有点重,可能是,不过我建议你做一些分析。Re:我需要它来追。。。事务,由触发器约束定义的可延迟性来处理,而不是由要执行的函数的定义来处理。对我所知的一方来说有点晚了,但是是否可以只执行一次有效负载函数,就像只执行最后一次更新stmt一样?
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;