为何带NOT EXISTS的INSERT...SELECT会触发PostgreSQL唯一约束冲突?
问题原因与解决方案
核心原因:并发竞态条件
你的语句在单会话执行时逻辑完全正确,但多会话并发执行时会出现竞态条件:
- 多个会话同时执行该INSERT语句时,各自先通过WITH子句生成
uniqstatements结果集,接着执行NOT EXISTS检查——此时所有会话都会发现目标queryid尚未被插入,因为还没有会话完成最终的插入操作。 - 当第一个会话完成插入后,后续会话再执行插入时,就会触发主键唯一约束冲突。
- 本质是
NOT EXISTS检查与INSERT操作并非原子性操作,中间存在时间窗口,导致重复插入。
另外存在极小概率的情况:不同查询文本生成了相同的queryid(哈希碰撞),但这种情况非常罕见,通常可忽略。
解决方案
最可靠且高效的方式是使用PostgreSQL的ON CONFLICT语法,将冲突处理逻辑原子化:
WITH uniqstatements AS ( SELECT ss.queryid, min(ss.query) query FROM pg_stat_statements AS ss GROUP by ss.queryid ) INSERT INTO perf.dba_hist_sqltext (queryid, query) SELECT ss.queryid, ss.query FROM uniqstatements AS ss ON CONFLICT (queryid) DO NOTHING;
该语法会在插入时自动检测主键冲突,遇到冲突则跳过该行,全程原子执行,彻底避免并发竞态导致的约束冲突。
若业务场景对并发性能要求不高,也可将事务隔离级别提升为SERIALIZABLE,强制事务串行执行,但会牺牲部分并发能力。
内容的提问来源于stack exchange,提问作者Jim Wartnick
相关产品推荐
相关产品推荐

