我有一个可以操纵任何表序列的通用函数(为什么与我的问题无关)。它读取当前值,计算出新值,设置它,并返回其计算,即插入的值。这显然是一个多步骤的过程。
我在需要它的表格上从BEFORE INSERT
触发器调用它。
我需要知道的是,我保证在多用户环境中一次只能由一个呼叫者调用该功能吗?
具体来说,BEFORE INSERT
触发器是否必须在另一个调用者再次调用之前完成?
从逻辑上讲,我会假设是的,但是人们永远都不知道幕后会发生什么。
如果答案是否定的,我需要在函数上进行最小的锁定,以保证我能以“线程安全”的方式读写序列?
我正在使用PG 10。
修改
以下是使用锁更新的功能:
CREATE OR REPLACE FUNCTION public.uts_set()
RETURNS TRIGGER AS
$$
DECLARE
sv int8;
seq text := format('%I.%I_uts_seq', tg_table_schema, tg_table_name);
BEGIN
EXECUTE format('LOCK TABLE %I IN ROW EXCLUSIVE MODE;', tg_table_name);
EXECUTE 'SELECT last_value+1 FROM ' || seq INTO sv; -- currval(seq) isn't useable
PERFORM setval(seq, GREATEST(sv, (EXTRACT(epoch FROM localtimestamp) * 1000000)::int8), false);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
但是,SELECT
已经获得ROW EXCLUSIVE
,因此此声明可能是多余的,可能需要更强的锁定。或者,相反,它可能意味着不需要锁定。
更新
如果我正确地阅读this SO question,我的原始版本没有LOCK应该可以工作,因为触发器获取了相同的锁,我的更新功能是冗余的。
答案 0 :(得分:1)
所有我需要知道的是,我保证在多用户环境中一次只能由一个呼叫者调用该功能吗?
没有。与调用函数本身无关,但您可以使用SERIALIZABLE事务隔离级别实现此行为:
此级别模拟所有已提交的串行事务执行 交易;好像交易一个接一个地执行, 连续的,而不是同时的
但是这种方法会引入一些权衡,例如准备应用程序以重新序列化失败重试事务。
也许错过了什么,但我真的相信你只需要NEXTVAL
,如下所示:
CREATE OR REPLACE FUNCTION public.uts_set()
RETURNS TRIGGER AS
$$
DECLARE
sv int8;
-- First, use %I wildcard for identifiers instead of %s
seq text := format('%I.%I', tg_table_schema, tg_table_name || '_uts_seq');
BEGIN
-- Second, you couldn't call CURRVAL on a session
-- that you didn't issued NEXTVAL before
sv := NEXTVAL(seq);
-- Do your logic here...
-- Result is ignored since this is an STATEMENT trigger
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
请记住,CURRVAL
对会话本地作用域和{strong>全球范围内的NEXTVAL
行事,因此您手中有可靠的线程安全机制
答案 1 :(得分:0)
序列本身通过并发会话处理线程安全性。所以它真实归结为与序列交互的代码。以下代码是线程安全的:
SELECT nextval('myseq');
如果序列正在做更多更好的事情,如setval
和currval
,我会更担心在高事务/多用户环境中完成。即便如此,序列本身也应该在操作序列时锁定其他查询。