INSERT ... ON DUPLICATE KEY UPDATE 的锁粒度与死锁风险分析

发布时间 - 2026-01-30 00:00:00    点击率:
ON DUPLICATE KEY UPDATE 会锁所有扫描到的唯一索引记录及其间隙,而非仅冲突行;并发插入可能因多唯一索引加锁顺序不一致导致死锁,需通过统一访问顺序、减少唯一约束、捕获死锁后重试来缓解。

ON DUPLICATE KEY UPDATE 会锁哪些行

它不是只锁“将要插入的那条记录”,而是先按 INSERT 路径走:对唯一索引(PRIMARY KEY 或 UNIQUE KEY)匹配的冲突行加 INSERT intention lock,再升级为 X lock;如果没冲突,则对插入位置加 gap locknext-key lock。关键点在于——只要涉及唯一索引查找,InnoDB 就会对**所有扫描到的唯一索引记录及其间隙**加锁,哪怕最终只更新其中一条。

常见误判是认为“只锁冲突行”,实际中:INSERT ... ON DUPLICATE KEY UPDATE 在唯一索引上执行时,会触发和 SELECT ... FOR UPDATE 类似的加锁行为,尤其当唯一索引非主键、或存在多个唯一约束时,锁范围可能意外扩大。

为什么两个并发 INSERT 可能死锁

典型死锁场景:事务 A 和 B 同时执行相同语句,但扫描/加锁顺序不同(比如因索引 B+ 树分裂、页分裂导致遍历路径不一致),或它们分别命中了不同唯一索引(如一个走 uk_email,另一个走 uk_phone),就可能形成循环等待。

  • 事务 A 先锁住 uk_email 上的某条记录 X,再尝试获取 uk_phone 上的记录 Y
  • 事务 B 先锁住 uk_phone 上的 Y,再尝试获取 uk_email 上的 X

MySQL 无法预判这种跨索引的锁依赖,只能在加锁失败时检测并回滚其中一个事务。这类死锁不会报 Lock wait timeout,而是直接返回 Deadlock found when trying to get lock

UPDATE 部分是否影响锁行为

不影响加锁范围,只影响是否释放锁。无论 UPDATE 子句有没有实际修改字段值(比如 SET status = status),只要语句进入 UPDATE 分支,就会持有被更新行的 X lock 直到事务结束。但注意:UPDATE 中引用的非唯一字段(如普通二级索引列)不会额外加锁,除非该字段出现在 WHERE 条件里且触发了索引扫描。

一个易忽略点:ON DUPLI

CATE KEY UPDATEUPDATE 部分不支持子查询或函数调用(如 SET ts = NOW() 是允许的,但 SET val = (SELECT ...) 会报错),这限制了部分动态赋值场景,也间接减少了因子查询引入的额外锁。

如何降低死锁概率

核心思路是让并发操作尽可能按相同顺序访问索引,减少不确定性。具体可做:

  • 确保 INSERT 的值在唯一索引上有稳定排序(例如插入前对 key 做哈希或归一化处理,避免随机字符串导致 B+ 树分裂不可控)
  • 尽量只定义一个唯一约束(最好是主键),避免多唯一索引交叉加锁
  • REPLACE INTO 替代?不行——它本质是 DELETE + INSERT,锁更重、还可能触发外键级联和触发器,死锁风险更高
  • 业务层加分布式锁?过度设计;更轻量的做法是捕获 Deadlock found when trying to get lock 后退避重试(指数退避,最多 3 次)

真正难调试的是那些不常复现的间隙锁竞争——比如两个事务恰好落在同一个 gap 区间内插入不同值,又都触发了 ON DUPLICATE 分支,此时锁行为高度依赖当前索引页状态,连 SHOW ENGINE INNODB STATUS 里的 TRANSACTIONS 都不一定能还原全貌。


# mysql  # ai  # 为什么  # sql  # 分布式  # for  # select  # 字符串  # 循环  # delete  # 并发  # 死锁  # 加锁  # 会报  # 锁住  # 重试  # 的是  # 主键  # 就会  # 子句  # 都不 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel全局作用域是什么_Laravel Eloquent Global Scopes应用指南  如何在阿里云完成域名注册与建站?  Android自定义listview布局实现上拉加载下拉刷新功能  php 三元运算符实例详细介绍  Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】  公司门户网站制作流程,华为官网怎么做?  Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】  Laravel的HTTP客户端怎么用_Laravel HTTP Client发起API请求教程  Laravel事件监听器怎么写_Laravel Event和Listener使用教程  昵图网官方站入口 昵图网素材图库官网入口  *服务器网站为何频现安全漏洞?  Laravel API资源类怎么用_Laravel API Resource数据转换  WordPress 子目录安装中正确处理脚本路径的完整指南  Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全  Laravel如何实现用户密码重置功能?(完整流程代码)  Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】  如何在沈阳梯子盘古建站优化SEO排名与功能模块?  焦点电影公司作品,电影焦点结局是什么?  北京网站制作的公司有哪些,北京白云观官方网站?  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  Linux网络带宽限制_tc配置实践解析【教程】  七夕网站制作视频,七夕大促活动怎么报名?  Laravel如何使用Gate和Policy进行授权?(权限控制)  Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程  如何在阿里云虚拟主机上快速搭建个人网站?  作用域操作符会触发自动加载吗_php类自动加载机制与::调用【教程】  国美网站制作流程,国美电器蒸汽鍋怎么用官方网站?  Laravel 419 page expired怎么解决_Laravel CSRF令牌过期处理  昵图网官网入口 昵图网素材平台官方入口  如何快速选择适合个人网站的云服务器配置?  nodejs redis 发布订阅机制封装实现方法及实例代码  如何快速上传建站程序避免常见错误?  Android使用GridView实现日历的简单功能  HTML 中如何正确使用模板变量为元素的 name 属性赋值  如何在 React 中条件性地遍历数组并渲染元素  Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】  Laravel如何使用Service Provider服务提供者_Laravel依赖注入与容器绑定【深度】  Laravel如何部署到服务器_线上部署Laravel项目的完整流程与步骤  Win11怎样安装网易有道词典_Win11安装词典教程【步骤】  Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南  Laravel如何升级到最新的版本_Laravel版本升级流程与兼容性处理  UC浏览器如何切换小说阅读源_UC浏览器阅读源切换【方法】  今日头条AI怎样推荐抢票工具_今日头条AI抢票工具推荐算法与筛选【技巧】  如何在橙子建站中快速调整背景颜色?  Laravel与Inertia.js怎么结合_使用Laravel和Inertia构建现代单页应用  微信小程序 require机制详解及实例代码  Laravel中的withCount方法怎么高效统计关联模型数量  Laravel路由Route怎么设置_Laravel基础路由定义与参数传递规则【详解】  如何在IIS中配置站点IP、端口及主机头?  高防服务器租用指南:配置选择与快速部署攻略