PostgreSQL中的递归BEFORE UPDATE触发器不使用最后返回的new

时间:2014-02-06 15:04:17

标签: postgresql triggers

我有一个带有更新前触发器的表,它可以通过更新同一行来触发自身,但更新语句的结果不是该触发器返回的最后一个值。我不明白为什么以及如何运作。这个问题在例子中更容易理解:

BEGIN;

SET client_min_messages = 'notice';

CREATE TABLE public.test (
  id INTEGER, 
  status INTEGER, 
  value numeric
);

CREATE SEQUENCE test_seq;

CREATE OR REPLACE FUNCTION public.test_tr() RETURNS trigger AS $body$
DECLARE
    seq integer;
BEGIN
    seq := nextval('test_seq');
    RAISE NOTICE 'Started: %', seq;
    IF new.status != old.status THEN
        UPDATE test SET value = new.value WHERE id = new.id;
    END IF;
    RAISE NOTICE 'Finished: %, RETURNED NEW: %', seq, new;
    RETURN new;
END
$body$ LANGUAGE 'plpgsql';

CREATE TRIGGER test_tr BEFORE UPDATE ON test FOR EACH ROW EXECUTE PROCEDURE test_tr();

INSERT INTO test (id, status, value) VALUES (1, 1, 1);

UPDATE test SET status = 2, value = 2 WHERE id = 1;

SELECT * FROM test;

ROLLBACK;

产生以下内容:

NOTICE:  Started: 1
NOTICE:  Started: 2
NOTICE:  Finished: 2, RETURNED NEW: (1,1,2)
NOTICE:  Finished: 1, RETURNED NEW: (1,2,2)
UPDATE 0

 id | status | value 
----+--------+-------
  1 |      1 |     2

正如您所看到的,最后返回的 new 是(1,2,2)(status = 2,value = 2),但最后一个SELECT返回“status”= 1且“value”的行“= 2.为什么状态=”1“?为什么不“2”?

1 个答案:

答案 0 :(得分:0)

BEFORE UPDATE触发器在同一行调用UPDATE时,手册似乎没有定义任何特定行为。

直观地说,我们可能会期望NEW的最后一个值进入行,但结果表明,这不是发生的事情。

我建议发生什么:

  1. 触发器发出更新#2。此时该行仍有其初始(1,1,1) 值。

  2. 触发器再次启动UPDATE#2,但它只返回NEW,因此不会改变任何内容。

  3. UPDATE#2完成后,该行的新版本将以(1,1,2)作为内容变为最新版本。

  4. 当UPDATE#1完成时,引擎应写入NEW=(1,2,2),但随后它会注意到它正在处理的行的版本已被取代。所以它丢弃了它的NEW记录,并将其留在那里。

  5. 这与psql报告UPDATE 0一致:UPDATE#1实际上并未更新任何行。