PostgreSQL BEFORE INSERT在并发环境中触发锁定行为

时间:2017-11-14 16:19:21

标签: postgresql concurrency triggers locking

我有一个可以操纵任何表序列的通用函数(为什么与我的问题无关)。它读取当前值,计算出新值,设置它,并返回其计算,即插入的值。这显然是一个多步骤的过程。

我在需要它的表格上从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应该可以工作,因为触发器获取了相同的锁,我的​​更新功能是冗余的。

2 个答案:

答案 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');

如果序列正在做更多更好的事情,如setvalcurrval,我会更担心在高事务/多用户环境中完成。即便如此,序列本身也应该在操作序列时锁定其他查询。

相关问题