PostgreSQL使用Serializable隔离级别未触发并发插入冲突错误排查
问题分析与解决方案
为什么Serializable隔离级别未触发冲突?
PostgreSQL的Serializable隔离级别核心是确保事务执行结果等价于某一种串行执行顺序。在你的场景中:
- 若串行顺序为「T1先执行,T2后执行」:T1计算
acc=2620的sum(amt)为110,插入user_id=-1、acc=2630、amt=110的行;随后T2插入user_id=737、acc=2620、amt=100的行。这个结果和你并发执行的实际结果完全一致。 - 由于存在合法的串行执行顺序匹配并发结果,Serializable不会判定为冲突,因此不会触发回滚。
实现需求的可行方案
要达成「基于指定acc的现有数据插入新行时,若存在针对该acc的并发插入则回滚T1」的目标,你需要显式锁定acc=2620的数据集,阻止其他事务插入同acc的新行,以下是两种常用方案:
1. 利用间隙锁阻止新行插入(推荐,并发影响较小)
前提是acc列已创建B-tree索引,然后在T1中先执行带锁的查询:
begin; set transaction isolation level serializable; -- 锁定acc=2620的索引范围,阻止插入新的同acc行 select sum(amt) from account where acc >= 2620 and acc <= 2620 for update; insert into account (user_id, acc, amt) values (-1, 2630, (select sum(sub.amt) from account sub where sub.acc = 2620)); commit;
这里的范围查询会触发PostgreSQL的间隙锁,覆盖acc=2620对应的索引区间,其他事务插入acc=2620的行时会被阻塞,直到T1提交或回滚;若T2先执行插入,T1的锁查询会等待,最终根据提交顺序决定是否触发冲突。
2. 使用表级锁(简单但并发性能低)
若业务并发量较低,可直接对表加锁阻止所有写操作:
begin; set transaction isolation level serializable; -- 阻止其他事务执行INSERT/UPDATE/DELETE lock table account in share row exclusive mode; insert into account (user_id, acc, amt) values (-1, 2630, (select sum(sub.amt) from account sub where sub.acc = 2620)); commit;
该方式会锁定整个表,所有其他写操作必须等待T1完成,适合并发需求不高的场景。
内容的提问来源于stack exchange,提问作者asdasd-mjeesh
相关产品推荐
相关产品推荐

