【发布时间】: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_id 或set_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