MySQL ON DUPLICATE KEY UPDATE 为什么别再用 VALUES():行别名改写和兼容边界
来源:17golang原创
时间:2026-07-18 12:53:05 117浏览 收藏
库存同步服务升级数据库后,日志里开始出现一条容易被忽略的提示:ON DUPLICATE KEY UPDATE 里用 VALUES(stock) 读取待写入值的形式已被标记为弃用。业务没有马上出错,但这类提示如果长期搁着,后续升级时就会变成一批分散的 SQL 改造。
改法不是把 VALUES() 换成随便一个函数。MySQL 现在建议在 VALUES 或 SET 子句后给即将插入的行起别名,再通过这个别名读取新行字段。对单行或多行写入,这个迁移很直观;对 INSERT ... SELECT,则需要额外留意查询来源的别名。
要点速览:
VALUES(col)在ON DUPLICATE KEY UPDATE中用于引用待插入行,但 MySQL 已将该用法标记为弃用。- 普通
INSERT ... VALUES可改为AS new,例如stock = new.stock。 INSERT ... SELECT不能机械套行别名,应按来源列别名或派生表的写法改造。
先看旧写法:它解决了什么问题
以库存快照表为例,sku 有唯一索引。同步任务拿到最新库存后,希望不存在就插入,已存在就更新库存和同步时间。以前常见的写法是:
INSERT INTO inventory_snapshot (sku, stock, synced_at)
VALUES ('A-100', 16, '2026-07-18 12:00:00')
ON DUPLICATE KEY UPDATE
stock = VALUES(stock),
synced_at = VALUES(synced_at);
VALUES(stock) 这里并不是普通的 VALUES 语句,它表示“如果没有唯一键冲突,本来会写入 stock 列的值”。这让 upsert 能同时保留旧行的主键和新行的业务字段。
问题在于,这种在更新子句中引用新行字段的方式已经被 MySQL 标为弃用,并说明未来版本可能移除。这里不必等业务报错再动手:先把语义替换为行别名,改动范围最容易控制。
第 1 步:给待插入的行起别名
把 AS new 放在 VALUES (...) 后面,更新子句就能通过 new.列名 读取同一批待写入数据:
INSERT INTO inventory_snapshot (sku, stock, synced_at)
VALUES ('A-100', 16, '2026-07-18 12:00:00') AS new
ON DUPLICATE KEY UPDATE
stock = new.stock,
synced_at = new.synced_at;
这一步的判断很简单:new.stock 指新行准备写入的库存,inventory_snapshot.stock 指当前已有行的库存。两者都叫 stock 时,把新行放在 new 下,阅读 SQL 的人就不必猜字段来自哪里。

VALUES() 移到显式行别名。第 2 步:多列写入时保持新旧字段的来源清楚
实际表往往不止更新一个字段。下面的写法把价格、库存、同步时间都来自新行,而 version 则基于旧行自增。这样的来源分工需要在 SQL 里直接看得出来:
INSERT INTO product_snapshot (sku, price, stock, synced_at, version)
VALUES ('A-100', 99.00, 16, '2026-07-18 12:00:00', 1) AS new
ON DUPLICATE KEY UPDATE
price = new.price,
stock = new.stock,
synced_at = new.synced_at,
version = product_snapshot.version + 1;
这里容易犯的错不是语法,而是语义:如果同步任务收到的是部分字段,直接用 new.字段 覆盖旧值,可能把本地维护的内容清掉。写入前先区分“外部快照覆盖列”“本地维护列”和“按旧值计算列”,再决定每个赋值表达式引用哪一边。
| 字段类型 | 推荐来源 | 示例 |
|---|---|---|
| 外部快照字段 | 新行别名 | stock = new.stock |
| 本地累计字段 | 已有行 | version = product_snapshot.version + 1 |
| 需要保留旧值的可选字段 | 显式条件 | note = COALESCE(new.note, product_snapshot.note) |
第 3 步:列别名能更短,但行别名仍然要保留
MySQL 还允许为插入列起别名。这样更新子句可以写得更短,不过列别名仍要依附一个行别名:
INSERT INTO inventory_snapshot (sku, stock, synced_at)
VALUES ('A-100', 16, '2026-07-18 12:00:00') AS new(code, qty, fetched_at)
ON DUPLICATE KEY UPDATE
stock = qty,
synced_at = fetched_at;
短别名适合字段很多、语句较长的导入任务;普通业务 SQL 里,我更建议先用 new.stock 这种可读性更好的形式。无论选哪种,别名不能与表名冲突,同一行上的列别名也应保持唯一。
第 4 步:INSERT ... SELECT 不要机械替换
INSERT ... SELECT ... ON DUPLICATE KEY UPDATE 是另一条路径。官方文档指出,在这类语句的更新子句里使用 VALUES() 会产生警告;推荐做法是让 SELECT 的输出字段有可引用的名称,必要时包一层派生表。
INSERT INTO inventory_snapshot (sku, stock) SELECT sku, stock FROM ( SELECT item_code AS sku, available_stock AS stock FROM supplier_snapshot ) AS src ON DUPLICATE KEY UPDATE stock = src.stock;
这段 SQL 的重点不在“多套一层查询”,而在于 src.stock 的来源是明确的。改造前先确认原 SELECT 的输出列名;如果来自联合查询或表达式,也给它们稳定的别名,回归测试时才能一眼看出写入字段是否串位。

VALUES() 更清晰。兼容检查:别把一次语法迁移当成无风险替换
改完后先在测试库跑两组数据:一组没有冲突,检查插入值;一组命中唯一键,检查更新列和保留列。MySQL 文档还说明,ON DUPLICATE KEY UPDATE 的受影响行数会随插入、更新、值未改变而不同,依赖受影响行数判断业务结果的代码也要一起核对。
表上存在多个唯一索引时更要谨慎。一次插入可能触发不止一个候选冲突,官方文档建议尽量避免在有多个唯一索引的表上依赖这类写入逻辑。这里先别急着批量替换;把唯一键设计、冲突字段和更新字段列成清单,再决定是否适合继续用 upsert。
- 搜索所有
ON DUPLICATE KEY UPDATE ... VALUES(形式,按普通插入和INSERT ... SELECT分组。 - 为每条 SQL 写出“新行字段、旧行字段、计算字段”的来源说明。
- 在测试数据中覆盖无冲突、命中冲突、值不变和多行导入四种结果。
- 把服务端版本能力纳入发布前置检查,旧环境不要直接上线只在新版本验证过的语法。
把 VALUES() 换成行别名,表面看是几处字符串变化,实质是把 upsert 里的新旧数据来源写得更明确。趁提示还只是提示时做完这一步,后面的数据库版本迁移会轻很多。
延伸问答
行别名必须叫 new 吗?
不是。可以使用其他合法别名,但不能与目标表同名。团队里固定一个直观名字会更方便审查 SQL。
能把 VALUES(col) 全部替换成 col 吗?
不能。没有前缀的列名通常指向已有行或会产生歧义。要引用待插入值,应使用行别名或列别名。
INSERT IGNORE 能解决唯一键冲突的更新问题吗?
不能把它当作通用替代。它会改变某些错误的处理方式,可能让部分行继续写入。是否使用应基于业务对警告和部分成功的容忍度单独判断。
相关阅读
- MySQL 库存怎么安全扣减?条件 UPDATE、防超卖和受影响行判断
- MySQL 幂等写入怎么设计:重复请求只创建一条订单的取舍
- MySQL ORDER BY LIMIT 为什么有时不走过滤索引:排序索引、filesort 和核对方法
核对资料
- MySQL 8.4 官方文档:INSERT ... ON DUPLICATE KEY UPDATE:
VALUES()弃用说明、行和列别名、INSERT ... SELECT 以及受影响行语义。
-
374 收藏
-
398 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
471 收藏
-
数据库 · MySQL | 1天前 | MySQL · 索引 · limit · explain · sql优化 · ORDER BY · mysql order by explain limit 复合索引 filesort279 收藏
-
数据库 · MySQL | 3天前 | 并发 · MySQL · InnoDB · update · 库存扣减 · innodb MySQL 库存扣减 条件 UPDATE 防超卖 affected rows470 收藏
-
421 收藏
-
189 收藏
-
412 收藏
-
378 收藏
-
334 收藏
-
259 收藏
-
468 收藏
-
数据库 · MySQL | 2星期前 | MySQL · InnoDB · 性能排查 · 故障复盘 · 长事务 · mysql PURGE 长事务 Undo history list length 写入延迟242 收藏
-
486 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习