ON CONFLICT DO NOTHING 有时“没效果”是因为指定的冲突列未被唯一约束覆盖,PostgreSQL不会自动推断,需确保该列存在UNIQUE或PRIMARY KEY约束,否则退化为普通INSERT;推荐用ON CONFLICT ON CONSTRAINT明确指定约束名。

ON CONFLICT DO NOTHING 为什么有时“没效果”却也不报错
它确实没报错,但也不等于数据一定进去了——常见于你指定的冲突目标(ON CONFLICT (col))和表上实际生效的唯一约束不匹配。PostgreSQL不会自动猜你想按哪条约束判断冲突,如果列组合没有对应唯一索引或主键,语句会静默退化为普通 INSERT,该报错还是报错。
验证方式很简单:\d 表名 查看输出中是否有 UNIQUE CONSTRAINT 或 PRIMARY KEY 明确覆盖你写的列。没有?那就得先建索引:CREATE UNIQUE INDEX ON 表名 (列名);
- 只对已存在唯一约束的列生效,不能靠“逻辑唯一”推断
- 若表有多个唯一约束(比如
(a)和(a,b)),写ON CONFLICT (a)可能触发任一约束,行为不确定 - 推荐改用
ON CONFLICT ON CONSTRAINT 约束名,避免歧义
批量插入时 DO NOTHING 的实际执行表现
在 INSERT INTO ... VALUES (...), (...), (...) 这种多行插入中,DO NOTHING 是按行粒度生效的:某一行冲突就跳过,其余行照常插入。不会因为其中一行冲突导致整条语句失败或回滚。
但要注意,并发场景下可能产生“幻读式冲突”:两个事务几乎同时检查、几乎同时插入,都以为没冲突,结果只有一个成功,另一个被 DO NOTHING 吞掉。这不是 bug,是 PostgreSQL speculative insert 机制的正常表现。
- 返回的
INSERT 0 1表示“1 行被处理”,INSERT 0 0表示“0 行被处理”(全冲突或全跳过) - 不锁住整个索引范围,高并发下吞掉重复数据是设计使然,不是性能问题
- 如果业务要求“必须插入至少一行”,得在外层加重试或用
DO UPDATE配合WHERE FALSE
DO NOTHING 和应用层先 SELECT 再 INSERT 的性能差异
别用 SELECT ... FOR UPDATE + INSERT 模拟 DO NOTHING,这会显著拖慢吞吐。前者要走两次网络往返、两次锁竞争;后者一次原子操作、底层用无锁乐观检查(speculative insert)。
实测在 1000 QPS 并发下,纯 DO NOTHING 插入延迟稳定在 0.8ms 左右;而先查后插平均飙升到 4.2ms,且随并发增长呈非线性恶化。
-
DO NOTHING在内核里走的是heap_insert→heap_finish_speculative路径,日志开销小 - 应用层判断引入竞态窗口,反而更容易丢数据或死锁
- 唯一例外是你要基于“当前值”做复杂决策(比如只在 count
容易被忽略的约束名引用细节
当你看到错误提示 there is no unique or exclusion constraint matching the ON CONFLICT specification,八成是因为你写了 ON CONFLICT (email),但实际约束名是 users_email_key(PostgreSQL 自动生成的)或你自己定义的 unique_user_email。
查约束名最准的方式不是猜,而是查系统表:SELECT conname FROM pg_constraint WHERE conrelid = '表名'::regclass AND contype IN ('u', 'p');
- 隐式约束名格式通常是
表名_列名_key,但一旦表结构变更(如删列重建),名字可能变 - 显式命名约束(
CONSTRAINT my_email_uniq UNIQUE (email))才是长期可维护的做法 -
DO NOTHING不支持WHERE条件,想加条件过滤就得切到DO UPDATE SET col = col WHERE FALSE










