在PostgreSQL里,当我们向一张带有唯一约束或主键的表插入数据时,如果待插入的行与已有行发生冲突,默认会抛出错误并终止当前语句。ON CONFLICT DO NOTHING是INSERT语句的一个可选子句,用来在检测到冲突时直接放弃该行插入,既不报错也不修改已有数据。这种机制常被称作upsert中的“仅忽略”模式,适合那些重复数据无业务危害、只求快速落地的场景。

一、基本语法与执行原理
ON CONFLICT子句必须出现在VALUES或查询部分之后,其基本写法为在INSERT末尾追加ON CONFLICT (冲突列) DO NOTHING,或者省略列名写成ON CONFLICT DO NOTHING由数据库自动匹配所有唯一约束。PostgreSQL在执行时会先按照指定的唯一索引进行冲突检测,如果发现重复键,就跳过该元组,继续处理后续记录。由于不需要回表更新,其开销通常小于DO UPDATE。
下面的例子创建一张用户标签表,并对user_id和tag做联合唯一约束,然后演示如何安全插入:
CREATE TABLE user_tag (
id serial PRIMARY KEY,
user_id int NOT NULL,
tag text NOT NULL,
UNIQUE (user_id, tag)
);
-- 重复插入相同(user_id, tag)时不会报错
INSERT INTO user_tag (user_id, tag)
VALUES (1001, 'vip')
ON CONFLICT (user_id, tag) DO NOTHING;
-- 也可省略冲突列,依赖表上已定义的唯一约束
INSERT INTO user_tag (user_id, tag)
VALUES (1001, 'vip')
ON CONFLICT DO NOTHING;
从执行计划看,DO NOTHING在冲突时仅做索引探测,不会获取行锁去修改堆数据,因此在高并发写入时锁竞争更小。但若表中存在多个唯一约束,省略列名可能导致意料之外的忽略,建议明确写出冲突目标。
二、与应用层去重对比
很多团队会在代码里先执行SELECT判断是否存在,再决定INSERT,这种做法在并发下容易因竞态产生重复或报错,且每次写入多出一次网络往返。使用ON CONFLICT DO NOTHING把判断下推到数据库内核,既能利用唯一索引的高效查找,也天然具备原子性。
以下Node.js伪代码展示了两种写法差异:
// 应用层先查后插(有并发隐患)
const exists = await db.query('SELECT 1 FROM user_tag WHERE user_id=$1 AND tag=$2', [uid, tag]);
if (!exists.rows.length) {
await db.query('INSERT INTO user_tag (user_id, tag) VALUES ($1,$2)', [uid, tag]);
}
// 直接使用ON CONFLICT DO NOTHING
await db.query(
'INSERT INTO user_tag (user_id, tag) VALUES ($1,$2) ON CONFLICT (user_id, tag) DO NOTHING',
[uid, tag]
);
后者代码更简洁,也避免了两次请求。在压测中,单条插入的QPS通常能提升两到三成。不过DO NOTHING不会返回被忽略的行数明细,如果业务需要统计跳过量,可以通过INSERT ... RETURNING结合CTE先试插再统计,但那就偏离了最简用法。
三、适用场景与注意事项
典型适用场景包括:日志型数据补录、消息幂等消费中的落库、离线批量导入等。这些场景的特点是允许重复丢弃,且希望写入不被异常打断。相反,若冲突时需要把旧记录的时间字段刷新,或累加计数,就必须改用ON CONFLICT DO UPDATE。
还需注意,DO NOTHING只忽略唯一约束和排除约束冲突,不满足CHECK约束仍会报错;另外在可延迟约束设置为DEFERRABLE时,冲突检测会推迟到事务提交,这期间看似插入成功,提交时仍可能失败。因此设计表结构时要明确约束的即时性。
-- 错误示例:列类型不符或违反非空约束仍会报错 INSERT INTO user_tag (user_id, tag) VALUES (null, 'vip') ON CONFLICT DO NOTHING; -- 违反user_id NOT NULL,不是唯一冲突,会直接异常
综合来看,ON CONFLICT DO NOTHING是PostgreSQL提供的轻量级冲突处理手段。理解其基于唯一索引的判定逻辑,并在合适的幂等写入场景中使用,可以省掉大量应用层容错代码,同时维持较好的写入性能。
PostgreSQLON_CONFLICT_DO_NOTHINGupsert修改时间:2026-08-11 00:06:24