导读:本期聚焦于小伙伴创作的《如何在PostgreSQL中高效处理冲突插入_利用INSERT ON CONFLICT DO NOTHING》,敬请观看详情。批量写入数据时如果目标表已存在主键或唯一约束冲突的行,传统做法先查询再插入会带来额外往返和并发竞态。PostgreSQL提供的INSERT ON CONFLICT DO NOTHING语法能在一条语句内完成冲突判定与忽略,显著降低应用层复杂度。该特性基于底层的唯一索引检测机制,在执行插入遇到重复键时直接跳过而非报错。相比手动捕获unique violation异常,它减少了异常处理开销,也避免了高并发下先查后插导致的幻读问题。实际使用中需注意它只忽略冲突行而不做任何更新,若需更新应改用DO UPDATE。合理设计约束与批量提交大小,可让数据同步与日志去重类任务吞吐明显提升。

在PostgreSQL里,当我们需要往一张带有主键或唯一索引的表中写入数据,而数据源里可能包含已经存在的记录时,如果直接执行普通的INSERT语句,数据库就会抛出唯一约束冲突的错误。为了避免这种情况,早期的做法往往是先执行一次SELECT查询确认记录是否存在,不存在再插入,或者捕获SQL异常后忽略。这类方式不仅增加了与数据库的交互次数,在并发场景下还容易出现竞态条件。PostgreSQL从9.5版本开始引入了INSERT ON CONFLICT子句,其中的DO NOTHING选项可以让数据库在碰到冲突行时自动跳过,而不中断整批写入,是实现高效冲突插入处理的推荐方案。

如何在PostgreSQL中高效处理冲突插入_利用INSERT ON CONFLICT DO NOTHING

一、INSERT ON CONFLICT DO NOTHING基本语法

该语法的核心是在INSERT语句后面追加ON CONFLICT冲突目标判定以及对应的处理动作。冲突目标通常指定为某个唯一约束、主键或者唯一索引列,当插入行与已有行在这些列上取值重复时,数据库便触发冲突处理逻辑。DO NOTHING表示直接忽略当前这一行,继续处理后续行。

下面给出一个最基础的示例,假设我们有一张用户表,user_id是主键:

CREATE TABLE users (
    user_id INTEGER PRIMARY KEY,
    user_name TEXT,
    create_time TIMESTAMP DEFAULT NOW()
);

-- 尝试插入,如果user_id冲突则什么也不做
INSERT INTO users (user_id, user_name)
VALUES (1001, '张三')
ON CONFLICT (user_id)
DO NOTHING;

上述语句第一次执行会正常插入,第二次以相同user_id执行时,PostgreSQL检测到主键冲突,便静默跳过,不会报错也不会改变原记录。这种方式比先查后插更简洁,也避免了应用层写复杂的判断逻辑。

二、为什么它比先查询再插入更高效

从执行原理上看,ON CONFLICT DO NOTHING是在同一个原子语句内部借助唯一索引的探测来完成判重。数据库在写入阶段就能发现重复键,并立即放弃该元组插入,不需要额外发起网络往返。而先SELECT再INSERT至少需要两次客户端与服务端交互,且在两次操作之间,其他事务可能插入相同记录,导致后者插入依然失败。

我们可以通过一个简单的对比来理解开销差异。以下代码展示了应用层常见的先查后插写法:

import psycopg2

conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()

def insert_if_not_exists(uid, name):
    cur.execute("SELECT 1 FROM users WHERE user_id = %s", (uid,))
    if cur.fetchone() is None:
        cur.execute("INSERT INTO users (user_id, user_name) VALUES (%s, %s)", (uid, name))
    conn.commit()

insert_if_not_exists(1001, '张三')

这种写法在每次插入前都要查询一次,批量导入十万条数据时就是十万次额外查询。如果改用单条SQL的ON CONFLICT DO NOTHING,或者使用批量插入配合该子句,就能把判重工作完全下推到数据库引擎,大幅减少开销。此外,在可重复读及以上隔离级别中,先查后插还可能因并发写入而出现序列化失败,而ON CONFLICT由数据库统一加锁处理,更加安全。

三、批量插入中的实际应用

在数据同步、日志去重等场景中,我们往往要一次性写入多行。PostgreSQL支持在VALUES后列举多行,并统一附加ON CONFLICT DO NOTHING,这样整批数据中只有冲突行被忽略,其余正常入库。

示例如下:

INSERT INTO users (user_id, user_name)
VALUES
    (1001, '张三'),
    (1002, '李四'),
    (1003, '王五')
ON CONFLICT (user_id)
DO NOTHING;

如果1002已经存在,那么只有1001和1003被插入,1002那一行被跳过,语句整体执行成功。对于ETL任务来说,这种语义非常友好:我们无需在抽取层做复杂去重,只需保证目标表约束正确,让数据库自己处理重复。

需要注意的是,若表上有多个唯一约束,可以通过指定冲突目标来精确控制。例如ON CONFLICT (email) DO NOTHING只忽略email冲突,而user_id冲突仍会报错。如果要忽略任意唯一约束冲突,可使用ON CONFLICT DO NOTHING而不写具体目标,但要求表上至少有一个唯一约束。

四、DO NOTHING与DO UPDATE的区别

很多初学者容易混淆ON CONFLICT的两种处理动作。DO NOTHING仅仅是跳过,原记录保持不动;而DO UPDATE可以在冲突时更新已有记录的部分字段,也就是俗称的upsert。

对比示例:

-- 冲突时跳过
INSERT INTO users (user_id, user_name)
VALUES (1001, '新名字')
ON CONFLICT (user_id)
DO NOTHING;

-- 冲突时更新名字
INSERT INTO users (user_id, user_name)
VALUES (1001, '新名字')
ON CONFLICT (user_id)
DO UPDATE SET user_name = EXCLUDED.user_name;

如果你的业务只关心“有这条数据就行,别报错”,那么DO NOTHING最合适;如果需要用最新数据覆盖旧数据,则必须选择DO UPDATE。错误使用DO NOTHING可能导致数据一直停留在旧状态,在排查问题时容易让人误以为写入成功却没生效。

五、使用时的注意事项与性能建议

首先,ON CONFLICT DO NOTHING依赖唯一索引或主键来判定冲突,如果表上没有相应约束,数据库无法识别哪些算冲突,语句虽能执行但起不到忽略作用。因此设计表结构时应提前规划好唯一性约束。

其次,在极大批量写入时,建议结合COPY或者分批事务提交,而不是在单个事务里堆入百万级INSERT。虽然ON CONFLICT本身高效,但事务过长会占用锁和日志空间。可以参考以下分批逻辑:

batch = []
for item in data_source:
    batch.append(item)
    if len(batch) >= 1000:
        cur.executemany(
            "INSERT INTO users (user_id, user_name) VALUES (%s, %s) "
            "ON CONFLICT (user_id) DO NOTHING",
            batch
        )
        conn.commit()
        batch.clear()

最后,如果在读写分离架构中,要注意ON CONFLICT只在主库执行,从库通过流复制同步结果。应用层不应假设插入被忽略后立刻能在从库查到最新状态,需考虑复制延迟。总体而言,合理使用INSERT ON CONFLICT DO NOTHING可以让PostgreSQL的冲突插入处理既安全又高效。

PostgreSQLINSERT_ON_CONFLICT冲突插入修改时间:2026-08-11 03:36:33

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。