PostgreSQL 实战:详解 UPSERT(INSERT ON CONFLICT)

文章目录

    • 一、UPSERT 基础
      • 1.1 为什么需要UPSERT?- 传统方案的缺陷
      • 1.2 替代方案对比
      • 1.3 跨数据库兼容性
      • 1.4 UPSERT 使用建议
    • 二、基本使用
      • 2.1 核心语法:`INSERT ... ON CONFLICT`
      • 2.2 突目标(Conflict Target)详解
      • 2.3 返回结果:`RETURNING` 子句
    • 三、高级技巧:精细化控制更新逻辑
      • 3.1 条件更新(避免无意义写入)
      • 3.2 部分字段更新(保留原值)
      • 3.3 累加操作(计数器场景)
      • 3.4 DO NOTHING:静默忽略冲突
      • 3.5 性能优化:索引与执行计划
    • 四、常见陷阱与避坑指南
      • 陷阱 1:冲突目标未命中索引
      • 陷阱 2:在 DO UPDATE 中引用非冲突列
      • 陷阱 3:忽略 NULL 值的特殊性
      • 陷阱 4:触发器行为异常
    • 五、Python + SQLAlchemy 实战
      • 5.1 原生 SQL 方式(推荐)
      • 5.2 SQLAlchemy 2.0 Core 方式

在现代应用开发中,“存在则更新,不存在则插入”是极其常见的数据操作模式,例如:

  • 用户首次访问时创建记录,后续访问更新最后登录时间
  • 电商商品库存的累加(而非覆盖)
  • 实时统计指标(如 PV/UV 计数器)
  • 缓存写入(缓存穿透场景)

PostgreSQL 从9.5 版本开始提供了标准 SQL 的INSERT ... ON CONFLICT语法(即UPSERT),彻底解决了这一痛点。本文将从基础用法、高级技巧、性能优化、避坑指南四个维度,带你全面掌握 UPSERT 的精髓。


一、UPSERT 基础

1.1 为什么需要UPSERT?- 传统方案的缺陷

在没有 UPSERT 之前,开发者通常采用两种方式:

1、方案 A:先查后插(Race Condition 风险)

# 伪代码ifnotdb.exists(user_id):db.insert(user_id,...)else:db.update(user_id,...)
  • 问题:高并发下可能多次插入(违反唯一约束)
  • 后果:程序崩溃或数据不一致

2、方案 B:捕获异常(性能差 + 逻辑复杂)

BEGIN;INSERTINTOusersVALUES(1,'Alice');EXCEPTIONWHENunique_violationTHENUPDATEusersSETname='Alice'WHEREid=1;END;
  • 问题:频繁抛异常开销大,代码冗长

UPSERT 的价值
原子性 + 高性能 + 简洁语法,一行 SQL 解决所有问题。

1.2 替代方案对比

方案优点缺点适用场景
UPSERT原子性、高性能、标准 SQL需 PG ≥ 9.5绝大多数场景首选
MERGE (SQL:2003)标准更通用PG 15+ 才支持跨数据库兼容
先查后插 + 锁逻辑清晰性能差、易死锁极低频操作
Rule 系统自动重定向复杂、难维护遗留系统

结论坚持使用INSERT ... ON CONFLICT,它是 PostgreSQL 社区验证的最佳实践。

1.3 跨数据库兼容性

数据库UPSERT 语法
PostgreSQLINSERT ... ON CONFLICT
MySQLINSERT ... ON DUPLICATE KEY UPDATE
SQLiteINSERT ... ON CONFLICT ... DO UPDATE
SQL ServerMERGE
OracleMERGE

若需跨数据库,可封装适配层,或使用Django ORM / SQLAlchemy的方言抽象。

1.4 UPSERT 使用建议

场景推荐做法
基础插入/更新ON CONFLICT (col) DO UPDATE SET ...
避免无意义更新添加WHERE条件(如时间比较)
计数器累加SET counter = table.counter + 1
静默忽略DO NOTHING
高性能批量写入多值VALUES或临时表
索引优化为冲突目标建唯一索引(CONCURRENTLY
Python 集成使用原生 SQL 或 SQLAlchemy Core

💡终极心法
“UPSERT 不是魔法,而是精心设计的原子操作。”
正确使用它,你的应用将获得数据一致性、高并发能力和简洁代码三重收益。


二、基本使用

2.1 核心语法:INSERT ... ON CONFLICT

1、基本结构

INSERTINTOtable_name(column1,column2,...)VALUES(value1,value2,...)ONCONFLICT[conflict_target]DOUPDATESETcolumn1=excluded.column1,column2=excluded.column2,...[WHEREcondition];

关键组件解析:

组件说明
conflict_target冲突检测目标(唯一索引/约束)
excluded虚拟表,代表尝试插入但冲突的行
DO UPDATE SET冲突时执行的更新操作
WHERE可选条件,控制是否更新

2、最简示例:存在则更新所有字段

假设用户表:

CREATETABLEusers(idSERIALPRIMARYKEY,emailVARCHAR(255)UNIQUENOTNULL,nameVARCHAR(100),last_loginTIMESTAMP);

UPSERT 操作:

INSERTINTOusers(email,name,last_login)VALUES('alice@example.com','Alice',NOW())ONCONFLICT(email)-- 冲突目标:email 唯一索引DOUPDATESETname=excluded.name,last_login=excluded.last_login;

✅ 效果:

  • email不存在 → 插入新行
  • email已存在 → 更新namelast_login

💡excluded.name表示“本次 INSERT 语句中提供的 name 值”

2.2 突目标(Conflict Target)详解

1、指定列(最常用)

ONCONFLICT(email)-- 基于 email 列的唯一约束

2、指定约束名(更精确)

-- 先创建命名约束ALTERTABLEusersADDCONSTRAINTuk_users_emailUNIQUE(email);-- 使用约束名ONCONFLICTONCONSTRAINTuk_users_email

3、部分索引(Partial Index)冲突

-- 创建部分唯一索引:仅对 active=true 的记录生效CREATEUNIQUEINDEXidx_active_emailONusers(email)WHEREactive=true;-- UPSERT 时指定该索引INSERTINTOusers(email,name,active)VALUES('bob@example.com','Bob',true)ONCONFLICT(email)WHEREactive=true-- 必须匹配部分索引条件DOUPDATESETname=excluded.name;

注意:WHERE active = true必须与索引定义一致,否则无法触发冲突检测!

2.3 返回结果:RETURNING子句

UPSERT 支持RETURNING,可获取实际插入或更新的行

INSERTINTOusers(email,name)VALUES('alice@example.com','Alice')ONCONFLICT(email)DOUPDATESETname=excluded.nameRETURNINGid,email,name,'inserted'ASaction;-- 但无法区分是插入还是更新!

如何区分插入 vs 更新?

方法 1:使用 CTE + 标记

WITHupsertAS(INSERTINTOusers(email,name)VALUES('alice@example.com','Alice')ONCONFLICT(email)DOUPDATESETname=excluded.nameRETURNING*,'updated'ASaction),insertedAS(INSERTINTOusers(email,name)SELECT'alice@example.com','Alice'WHERENOTEXISTS(SELECT1FROMusersWHEREemail='alice@example.com')RETURNING*,'inserted'ASaction)SELECT*FROMupsertUNIONALLSELECT*FROMinserted;

复杂且有竞态风险,不推荐

方法 2:应用层判断(推荐)

  • 执行 UPSERT 前先查是否存在
  • 或通过业务逻辑推断(如首次注册 vs 登录)

现实建议:大多数场景无需区分,直接使用RETURNING获取最新数据即可。


三、高级技巧:精细化控制更新逻辑

3.1 条件更新(避免无意义写入)

场景:只在新登录时间 > 旧时间时才更新

INSERTINTOusers(email,last_login)VALUES('alice@example.com','2026-01-25 10:00:00')ONCONFLICT(email)DOUPDATESETlast_login=excluded.last_loginWHEREusers.last_login<excluded.last_login;-- 仅当新时间更新时才更新

3、优势:

  • 减少 WAL 日志
  • 避免触发不必要的触发器
  • 提升性能(尤其高频更新场景)

3.2 部分字段更新(保留原值)

场景:只更新last_login,不修改name

INSERTINTOusers(email,name,last_login)VALUES('alice@example.com','OldName',NOW())-- name 值会被忽略ONCONFLICT(email)DOUPDATESETlast_login=excluded.last_login;-- 不更新 name

💡 即使 INSERT 中提供了name,只要DO UPDATE SET不包含它,就不会被修改。


3.3 累加操作(计数器场景)

场景:用户访问次数 +1

INSERTINTOuser_visits(user_id,visit_count)VALUES(123,1)ONCONFLICT(user_id)DOUPDATESETvisit_count=user_visits.visit_count+1;-- 累加而非覆盖

安全替代:

-- 更健壮:防止初始值为 NULLDOUPDATESETvisit_count=COALESCE(user_visits.visit_count,0)+1;

3.4 DO NOTHING:静默忽略冲突

场景:只插入新记录,冲突时不做任何操作

INSERTINTOlogs(event_id,data)VALUES('evt_001','{"action":"click"}')ONCONFLICT(event_id)DONOTHING;-- 冲突时直接跳过

返回:受影响行数为 0(可通过RETURNING *验证是否插入)

3.5 性能优化:索引与执行计划

1、必建索引

UPSERT 的性能完全依赖冲突目标上的索引

-- 对 ON CONFLICT (email) 必须有唯一索引CREATEUNIQUEINDEXCONCURRENTLY idx_users_emailONusers(email);

使用CONCURRENTLY避免锁表(生产环境必备)

2、执行计划分析

EXPLAIN(ANALYZE,BUFFERS)INSERTINTOusers(email,name)VALUES('test@example.com','Test')ONCONFLICT(email)DOUPDATESETname=excluded.name;

关键观察点:

  • Index Only Scan:理想情况(仅扫描索引)
  • Heap Fetches:越少越好(表示需回表)
  • Buffersshared hit高表示缓存命中率高

3、批量 UPSERT(高性能写入)

单条 UPSERT 有网络开销,批量操作更高效:

-- 方式 1:多值插入INSERTINTOusers(email,name)VALUES('a@example.com','A'),('b@example.com','B'),('c@example.com','C')ONCONFLICT(email)DOUPDATESETname=excluded.name;-- 方式 2:从临时表导入CREATETEMPTABLEtemp_users(emailTEXT,nameTEXT);-- ... 填充临时表INSERTINTOusersSELECT*FROMtemp_usersONCONFLICT(email)DOUPDATESETname=excluded.name;

性能对比(10万条):

方式耗时
单条循环~30 秒
批量 VALUES~2 秒
临时表 + COPY~1 秒

四、常见陷阱与避坑指南

陷阱 1:冲突目标未命中索引

-- 表有唯一索引 (email, status)-- 但 UPSERT 只指定 (email)ONCONFLICT(email)-- ❌ 无法触发冲突!

✅ 解决:冲突目标必须与唯一索引完全匹配

陷阱 2:在 DO UPDATE 中引用非冲突列

-- 唯一索引是 (email)-- 但更新时引用了 id(非冲突列)DOUPDATESETid=excluded.id-- ❌ 可能导致主键冲突!

✅ 解决:只更新非唯一约束列

陷阱 3:忽略 NULL 值的特殊性

-- 唯一索引允许 NULL 重复INSERTINTOt(nullable_col)VALUES(NULL);INSERTINTOt(nullable_col)VALUES(NULL);-- 不会冲突!

✅ 理解:PostgreSQL 中 NULL != NULL,唯一索引允许多个 NULL

陷阱 4:触发器行为异常

  • BEFORE INSERT触发器在冲突时不会执行
  • BEFORE UPDATE触发器在DO UPDATE时会执行
  • 需要测试触发器逻辑是否符合预期

五、Python + SQLAlchemy 实战

5.1 原生 SQL 方式(推荐)

fromsqlalchemyimporttextdefupsert_user(session,email,name):stmt=text(""" INSERT INTO users (email, name, last_login) VALUES (:email, :name, NOW()) ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name, last_login = EXCLUDED.last_login RETURNING id; """)result=session.execute(stmt,{"email":email,"name":name})returnresult.scalar()

5.2 SQLAlchemy 2.0 Core 方式

fromsqlalchemyimportinsert stmt=(insert(users_table).values(email="alice@example.com",name="Alice").on_conflict_do_update(index_elements=["email"],set_=dict(name="Alice",last_login=func.now())).returning(users_table.c.id))

注意:SQLAlchemy ORM不直接支持 UPSERT,需用 Core 层或原生 SQL。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1214614.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爬虫部署:从零到一讲述 Supervisor 的详细使用

更多内容请见: 《爬虫和逆向教程》 - 专栏介绍和目录 文章目录 一、为什么需要 Supervisor? 1.1 爬虫运行的典型痛点 1.2 Supervisor 的核心价值 1.3 替代方案对比 1.4 Supervisor 部署建议 二、安装与基础配置 2.1 安装 Supervisor 2.2 主配置文件结构 2.3 启动 Supervisor …

函数指针的初级学习

函数指针 函数指针是什么? 函数指针即为结合函数和指针,让函数也存在地址,并且让函数指针来存储函数的地址 函数指针有什么用? 注:(AI生成,仅供参考,目前我还没有用到函数指针)灵活切换执行逻辑:不用修改核心…

2026年第一季度专业的长沙GEO运营团队哪家权威

一、行业背景与市场趋势 随着短视频平台流量红利见顶,企业线上获客的成本与难度日益攀升。在此背景下,一种名为GEO(地理定位)的精细化运营策略正成为企业,尤其是依赖本地市场或特定区域客户的企业,实现高效获客的…

2026 年1月 geo 优化公司标杆企业观察:技术创新驱动下的增长赋能能力解析

报告出品方参考:易观分析 核心定位:基于GEO行业的发展现状,梳理市场特性、技术走向以及落地场景,输出契合不同需求的核心品牌推荐。 一、行业概况 1.1 市场规模与增长态势 2025年,我国GEO市场规模达到2.5亿元,采…

新手如何用Python调用中转API搭建ChatGPT聊天应用?

新手如何用Python调用中转API搭建ChatGPT聊天应用?你是否遇到过这样的场景?想把 ChatGPT 接入自己的应用,却被模型限制、价格波动、接口差异搞得一头雾水;刚写好的代码,换一个模型就要重构一遍;好不容易跑通了,…

面试官:Git 如何撤回已 Push 的代码?问倒一大片。。。

面试官问&#xff1a;Git 如何撤回已 Push 的代码&#xff1f;如果问你&#xff0c;你会吗&#xff1f; 在日常的开发中&#xff0c;我们经常使用Git来进行版本控制。有时候&#xff0c;我们可能会不小心将错误的代码 Push 到远程仓库&#xff0c;或者想要在本地回退到之前的某…

抖音代运营源头厂家推荐,2026年合作的不二之选,短视频代运营/抖音推广/短视频获客,抖音代运营老牌公司哪家好

在短视频营销成为企业核心获客渠道的当下,抖音代运营服务的质量直接影响品牌曝光、用户转化及长期运营效能。作为第三方专业评测机构,我们基于行业权威机构近期数据,从资质认证、技术实力、产品性能实测、项目案例、…

瞧瞧别人家的优惠券过期方案,那叫一个优雅!

前言如何在今晚零点&#xff0c;让1000万张优惠券在同一瞬间准时失效&#xff0c;同时保证系统平稳运行、用户无感知&#xff1f;这看似简单的需求背后&#xff0c;隐藏着对高并发架构设计的深刻考验。电商大促活动结束后&#xff0c;如何处理海量优惠券的集中过期&#xff0c;…

2026年1月geo优化服务商Top10:从本地化优化到全域增长的核心竞争力

当生成式AI重构搜索生态,GEO(生成式引擎优化)已从可选营销补充升级为企业全域增长的核心基建。2026年,全球GEO服务市场规模预计突破78亿美元,年增长率超85%,行业竞争从单一技术比拼转向“本地化深度适配+全域增长…

学长亲荐10个一键生成论文工具,自考本科毕业论文轻松搞定!

学长亲荐10个一键生成论文工具&#xff0c;自考本科毕业论文轻松搞定&#xff01; AI 工具的崛起&#xff0c;让论文写作不再难 在自考本科的道路上&#xff0c;毕业论文无疑是一道难以逾越的难关。面对繁杂的选题、漫长的写作过程以及反复的修改要求&#xff0c;许多学生常常感…

2026专业的通过式抛丸机公司推荐,哪个口碑好?

2026年工业制造智能化升级加速,通过式抛丸机作为金属工件表面处理的核心设备,已成为汽车零部件、钢结构、工程机械等行业提升生产效率、保障产品质量的关键支撑。无论是解决传统人工打磨的低效污染问题,还是适配新能…

探讨美酒瑶商贸评价如何,详细了解其合作品牌情况

本榜单依托酒水行业全维度市场调研与真实消费者口碑,深度筛选出五家标杆酒水商贸企业,为酒友甄选靠谱采购渠道提供客观依据,助力精准匹配适配的酒水服务伙伴。 TOP1 推荐:杭州美酒瑶商贸有限公司 推荐指数:★★★…

2026年专业的宣传片拍摄企业排名,南昌地区哪家好

2026年企业品牌竞争加剧,专业宣传片已成为企业传递实力、促进商业转化的核心载体。无论是适配招商洽谈的精准内容输出、覆盖会展发布会的场景化展示,还是助力品牌形象升级的全链路传播,优质宣传片拍摄企业的专业能力…

2026年实力强的叉车租赁公司排名,尚雅机械值得关注

在物流仓储与工业生产的核心场景中,叉车作为搬运利器的重要性不言而喻,而专业的叉车租赁服务则是企业降本增效的关键支撑。面对市场上良莠不齐的叉车租赁商,如何找到口碑好、实力强的合作伙伴?以下结合行业类型与服…

2026年长沙短视频运营服务商深度评测与选型指南

在2026年的当下,短视频已成为企业营销的“必争之地”。对于长沙及湖南地区的企业而言,选择一个技术扎实、效果可视的短视频运营服务商,是撬动线上增长的关键一步。面对市场上林林总总的服务商,决策者常常陷入困惑:…

2026年北京热门旅行社盘点,启程国际旅行社国内游线路多不多?

在文旅融合与科技赋能成为行业主流的当下,选择一家专业可靠的旅行社,直接关系到出行体验的品质与安全。面对市场上良莠不齐的旅游服务提供商,游客往往会产生XX旅行社怎么样?线路是否丰富?工作人员是否专业?的疑问…

2026年长沙短视频精准引流服务商综合评测与选型指南

随着短视频平台商业生态的日益成熟,利用短视频进行精准引流获客已成为企业营销的标配。特别是在长沙这座“中国媒体艺术之都”,新媒体营销氛围浓厚,服务商林立。面对2026年上半年的市场机遇,企业如何从众多服务商中…

2026上半年长沙IP打造机构综合实力榜与深度解析

面对短视频流量红利逐渐转向“留量”运营的今天,企业IP化已成为品牌突围、高效获客的核心战略。尤其在长沙这座“中国媒体艺术之都”,IP打造服务商如雨后春笋,选择哪家合作伙伴,直接关系到企业数字化营销的成败。本…

2026年成都地区整体无缝旗杆制造实力厂商综合评估

在市政形象升级、企事业单位文化标识需求精细化及重大活动保障标准日益提升的背景下,整体无缝旗杆制造技术已成为城市风貌与机构形象建设的核心驱动力之一。整体无缝工艺不仅关乎旗杆的美观度与耐久性,更直接影响到升…

2026年美妆和消费品行业DeepSeek优化服务商选择指南:从技术到效果选型

当生成式AI用户规模突破5.15亿,信息获取的“对话生成”范式全面取代传统搜索,GEO(生成引擎优化)已成为美妆和消费品行业抢占AI算法可见性、构建品牌信任资产的核心营销基建。2026年我国GEO市场规模预计飙升至30亿元…