【问题标题】:Postgres trigger procedure seems to be memoizedPostgres 触发程序似乎被记住了
【发布时间】:2015-11-11 13:19:47
【问题描述】:

我正在尝试在 postgresql 中创建一个触发器,以便在插入之前自动设置一些字段,但是我计算值的过程在每个会话中只调用一次,而不是在每次插入时调用。这是我试图实现这一目标的方法,

创建测试表

>> create table test (
    id varchar(16) unique not null
);

尝试插入表格而不提供id 的值

>> insert into test DEFAULT VALUES;
ERROR:  null value in column "id" violates not-null constraint

正如预期的那样,id 不能是 null

创建一个函数和触发器,在插入之前自动设置id

>> create or replace function set_id() returns trigger language plpgsql as $$                                                             
begin                                                            
    new.id = gen_custom_unique_id();
    return new;
end $$;


>> CREATE TRIGGER client_update_trigger BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE set_id();

gen_custom_unique_id 是另一个自定义函数。

试图向表中插入行,

>> insert into test DEFAULT VALUES;
INSERT 0 1

>> select id from test;
    id
------------------
  0_EQOEatMNaXO2a-
(1 row)

.. 按预期工作。

问题

尝试插入另一行

>> insert into test DEFAULT VALUES;
ERROR:  duplicate key value violates unique constraint "test_id_key"
DETAIL:  Key (id)=(0_EQOEatMNaXO2a-) already exists.

我的理解是,这应该再次调用过程set_id 并生成一个新的唯一ID,但这只会在每个会话中调用一次set_id。如果我断开与数据库的连接并尝试新会话,INSERT 将再次工作。

EXECUTE PROCEDURE set_id(); 看起来确实很可疑,因为我们正在调用内联过程。直观地说,我认为应该是EXECUTE PROCEDURE set_id;,然后触发器应该在插入每一行之前“调用”set_id,但这不起作用。

如何将EXECUTE PROCEDURE set_id(); 变成EXECUTE PROCEDURE lazy(set_id()); 之类的东西?

更新

我确定gen_custom_unique_id 工作正常。该代码太冗长且无关紧要,无法在 IMO 此处分享。为了这个例子,我们可以假设它只返回一个 UUID 或时间戳。我已经测试过它可以按预期工作,这是输出。

>> select * from gen_custom_unique_id();
   gen_custom_unique_id
------------------
 0_Elu4sdjRxW6s_s
(1 row)

>> select * from gen_custom_unique_id();
   gen_custom_unique_id
------------------
 0_EluVMKwLjJQP2s
(1 row)

>> select * from gen_custom_unique_id();
   gen_custom_unique_id
------------------
 0_ElutuejkWhR07N
(1 row)

>> select * from gen_custom_unique_id();
   gen_custom_unique_id
------------------
 0_ElvHrif6X2pQH-
(1 row)

>> select * from gen_custom_unique_id();
   gen_custom_unique_id
------------------
 0_ElvgXWgGIMYeHk
(1 row)

此函数生成类似 firebase 或 simpleflake ID (http://akmanalp.com/simpleflake_presentation/#/)

更新 2

我做了一些进一步的测试,看起来get_custom_unique_idset_id 的输出确实被缓存了。

我在get_custom_unique_id 中添加了一条 NOTICE 语句,它仅在触发器第一次调用该函数时打印。

连续调用gen_custom_unique_id 3次

>> select * from gen_custom_unique_id();
NOTICE:  generating id: 0_Il_GKmrwCwkUJJ
   gen_custom_unique_id
------------------
 0_Il_GKmrwCwkUJJ
(1 row)

>> select * from gen_custom_unique_id();
NOTICE:  generating id: 0_Il_xBXWXvLuwBk
   gen_custom_unique_id
------------------
 0_Il_xBXWXvLuwBk
(1 row)

>> select * from gen_custom_unique_id();
NOTICE:  generating id: 0_IlbKk0ixgnkIdA
   gen_custom_unique_id
------------------
 0_IlbKk0ixgnkIdA
(1 row)

注意它是如何打印NOTICE: generating id: {generated_id}的。

从触发器调用

>> insert into test DEFAULT VALUES;
NOTICE:  generating id: 0_IsgcquLRup1KxF
CONTEXT:  SQL statement "SELECT gen_custom_unique_id()"
PL/pgSQL function set_id() line 3 at assignment
INSERT 0 1

>> insert into test DEFAULT VALUES;
ERROR:  duplicate key value violates unique constraint "test_pkey"
DETAIL:  Key (id)=(0_IsgcquLRup1KxF) already exists.

来自gen_custom_unique_id 的通知仅在第一次调用时打印,因此绝对不会每次都调用它。

【问题讨论】:

  • 有什么理由使用连载吗?
  • 那么gen_custom_unique_id() 是做什么的?
  • @a_horse_with_no_name 它使用自定义算法生成唯一 ID。在这个例子中,我们可以安全地假设它返回一个 UUID、一个时间戳甚至一个序列。
  • EXECUTE PROCEDURE set_id(); 不会“调用”该过程。它只是“告诉”触发器在触发器触发时调用该函数。
  • 该功能是否涉及使用current_timestamp(或now())?您知道这会返回事务开始的时间吗?如果它确实使用了它,那么您的触发器在使用clocktimestamp() 时是否正常工作?如果您在单个事务中进行了两次插入,这将解释为什么第二次调用返回相同的 id。但是如果没有实际的代码,这是不可能的。触发器和触发器函数都不记得任何状态。唯一可能出错的地方是您生成 id 的函数。

标签: sql database postgresql plpgsql


【解决方案1】:

供您参考:将(调用)custom 函数连接为列的默认值,其方式与在 postgres 中实现序列的方式相同。它不需要触发器。我用 clock_timestamp() 作为 sequence-generating 函数来演示:

CREATE TABLE omg
              -- NOTE: clock_timestamp is volatile by nature,
              -- which means that the query-optimiser cannot presume
              -- that calling it twice would result in the same result
              -- (THUS: the function is called anytime a value is needed)
    ( id timestamp NOT NULL PRIMARY KEY DEFAULT clock_timestamp()
    , payload text
    );

INSERT INTO omg(payload) VALUES ('one' );
INSERT INTO omg(payload) VALUES ('two' );
SELECT * FROM omg;

结果:

CREATE TABLE
INSERT 0 1
INSERT 0 1
             id             | payload 
----------------------------+---------
 2015-11-11 15:29:07.330905 | one
 2015-11-11 15:29:07.338803 | two
(2 rows)

【讨论】:

  • 我实际上试过这个,但它并没有像我预期的那样工作。我不记得确切的问题是什么。稍后再试
  • 这种方法的问题是我无法控制应用程序,并且应用程序总是将默认值(null 或 '')发送到不允许 postgres 执行的数据库拥有。
  • 杀死应用程序。 (或者不允许它连接,首先;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多