批量更新主键排序防死锁 标准化规范(错误案例+正确案例+原理复盘)
批量更新主键排序防死锁 标准化规范错误案例正确案例原理复盘一、核心概述数据库批量更新、批量删除场景下90%偶发性死锁、无规律锁超时故障均源于「多线程事务加锁顺序不一致」。该类问题本地单线程测试100%正常仅线上高并发场景触发属于典型的“魔鬼细节隐形故障”。根治核心方案所有批量更新/批量删除执行业务SQL前必须按照数据表主键ID统一升序排序强制全局锁获取顺序一致彻底杜绝循环等待死锁。本规范适用于MySQL InnoDB、达梦DM8所有业务项目统一编码标准。二、故障底层原理深度复盘2.1 锁竞争核心逻辑InnoDB 数据库行锁特性事务执行批量更新时会按照待更新集合的遍历顺序逐条申请排他行锁。成功获取一行锁就执行该行数据修改如果某一行锁获取失败事务会发生阻塞已经获取到的行锁会继续保持持有不会释放。行锁生命周期绑定事务所有行锁统一在事务 commit/rollback 时才释放事务未提交前已持有的行锁会持续阻塞其他并发事务。行锁逐条申请事务结束统一释放中间阻塞已得锁不释放。2.2 死锁产生的必要条件缺一不可多线程并发执行批量更新不同线程的待更新数据列表主键顺序不一致线程互相持有对方需要的行锁形成循环等待2.3 场景模拟真实线上死锁流程假设存在两条数据主键ID140424、ID140425两个业务线程并发批量更新这两条数据线程A加锁顺序140424 → 140425线程B加锁顺序140425 → 140424死锁执行链路线程A 成功锁定 140424等待锁定 140425线程B 成功锁定 140425等待锁定 140424两个线程互相持有对方所需锁资源无限等待数据库死锁检测机制触发强制回滚其中一个事务业务抛出死锁异常死锁### 2.4 场景深度分析场景分析线程A上锁顺序140424 → 140425线程B上锁顺序140425 → 140424这个场景在【应用层for循环逐条执行update】的前提下真的会产生死锁线上非常常见。时序拆解for循环逐条DML不是单条IN线程A事务拿到140424行锁线程B事务拿到140425行锁线程A想去拿140425锁被B持有 → A阻塞等待B释放线程B想去拿140424锁被A持有 → B阻塞等待A释放A等B、B等A形成循环等待InnoDB检测到死锁回滚其中一个事务抛出1213错误。这就是真实线上死锁。⚠️前提必须是循环多次独立update语句每条SQL只更新一行。如果是单条update xxx where id in(140424,140425)不管你in里面id书写顺序怎么写InnoDB会按照聚簇索引从小到大顺序上锁两个线程上锁顺序永远是140424→140425不会死锁。为什么线上这个坑特别多ID集合来源是MQ、前端、RPC返回ID顺序完全随机本地单线程测试不会出现交错时序永远复现不了只有高并发多线程才会出现上面的“交叉拿锁”时序偶发复现条件苛刻属于典型“线上才出、本地好好的”隐形bug。举个极简伪代码就是这个故障的原型//线程A ListLong listA Arrays.asList(140424L,140425L); for(Long id : listA){ updateById(id); //每条都是独立SQL } //线程B ListLong listB Arrays.asList(140425L,140424L); for(Long id : listB){ updateById(id); //每条都是独立SQL }上面代码高并发跑就有概率触发死锁。解决办法循环前把list按主键升序排序。排序后两个线程上锁顺序完全一致就破坏死锁的必要条件。容易混淆的关键点总结执行模式是否会出现截图里的死锁解决手段Java for循环逐条update多条独立SQL✅会线上高频业务层对ID集合主键升序排序单条SQLupdate ... id in(?,?)❌不会内核自动按主键排序上锁业务层排序ID无意义不需要处理补充思考很多网上错误Demo错误拿in()去演示这个死锁场景这就是我们上一篇md博文勘误的那个点。死锁模型本身没问题但是演示工具选错了用in()是复现不出来这个交叉锁必须用循环多条DML。三、错误代码案例线上高频坑核心问题直接使用上游传入的无序List批量更新未做主键排序完全依赖上游数据顺序高并发必触发死锁。/** * 【错误示例】批量更新告警数据高危会产生死锁 * 问题点未排序上游返回的list顺序随机多线程并发锁顺序混乱 */ Transactional(rollbackFor Exception.class) public void batchUpdateAlarm(ListAlarmInfo updateList) { // 直接执行批量更新无任何排序处理 alarmInfoMapper.batchUpdate(updateList); }错误代码风险说明前端、MQ、上游接口返回的数据列表顺序完全随机单线程测试无任何问题无法复现bug线上高并发场景下随机触发死锁、锁超时日志偶发报错极难排查MySQL、达梦数据库均存在该问题无数据库版本豁免四、正确代码案例生产级标准写法核心优化批量操作前强制根据数据表主键ID升序排序统一全局加锁顺序彻底消灭循环等待。/** * 【正确示例】批量更新告警数据防死锁标准写法 * 优化点批量更新前强制主键升序排序 */ Transactional(rollbackFor Exception.class) public void batchUpdateAlarm(ListAlarmInfo updateList) { // 核心必写根据数据库主键ID 升序排序统一全局锁顺序 updateList.sort(Comparator.comparing(AlarmInfo::getId)); // 排序后执行批量更新杜绝死锁 alarmInfoMapper.batchUpdate(updateList); }五、进阶增强版生产终极方案排序去重分批降级适配大数据量、超高并发场景除排序防死锁外规避重复更新、大批量锁堆积、批量全败问题。/** * 【生产终极方案】批量更新 防死锁防重复防锁堆积异常降级 */ Transactional(rollbackFor Exception.class) public void safeBatchUpdateAlarm(ListAlarmInfo updateList) { // 1. 根据主键去重防止同事务重复锁同一行触发约束冲突 ListAlarmInfo distinctList updateList.stream() .collect(Collectors.toMap(AlarmInfo::getId, Function.identity(), (k1, k2) - k1)) .values() .stream() .collect(Collectors.toList()); // 2. 主键升序排序【防死锁核心代码】 distinctList.sort(Comparator.comparing(AlarmInfo::getId)); // 3. 小批量拆分单次50条避免一次性持有大量行锁减少锁超时 ListListAlarmInfo partitionList ListUtils.partition(distinctList, 50); // 4. 分批执行异常降级单条重试避免整批回滚 for (ListAlarmInfo partList : partitionList) { try { alarmInfoMapper.batchUpdate(partList); } catch (DataIntegrityViolationException | LockTimeoutException e) { // 批量冲突降级单条更新保证大部分数据执行成功 log.warn(批量更新锁冲突降级单条执行, e); partList.forEach(info - { try { alarmInfoMapper.updateById(info); } catch (Exception ex) { log.error(单条告警更新失败id:{}, info.getId(), ex); } }); } } }六、关键知识点答疑开发高频疑问6.1 为什么排序就能彻底杜绝死锁所有线程、所有批次统一按照主键从小到大加锁所有人抢锁顺序完全一致只会出现「后线程等待前线程」不会出现双向循环等待从算法层面直接消灭死锁必要条件。6.2 批量新增需要排序吗不需要。批量新增是写入新行、加短暂行锁不存在互相锁对方数据的场景无死锁风险无需排序。6.3 批量删除需要排序吗必须排序。批量删除同样是逐条加行锁锁机制和批量更新完全一致无序删除同样会触发死锁需按主键升序排序。6.4 非主键唯一字段可以排序吗不建议。必须使用物理主键ID排序唯一索引、业务ID无法替代保证锁顺序绝对唯一、稳定。七、强制编码规范纳入代码评审卡点所有batchUpdate / 批量update / 批量delete代码必须存在主键升序排序逻辑无排序一律打回。禁止直接使用上游原始List执行批量DML操作必须经过排序、去重处理。高并发业务批量操作强制使用「排序去重小批量拆分异常降级」终极方案。该规范同时适配 MySQL、达梦DM8全项目统一执行。八、故障总结批量更新死锁不属于数据库bug是典型的编码细节缺失导致的人为故障。问题隐蔽性极强、复现难度极高、线上危害极大仅需一行排序代码即可100%根治是所有后端开发必须掌握的标准化细节规范。九、场景复现更新死锁问题

相关新闻

二叉树_堆的实现

二叉树_堆的实现

1.树树是一种非线性的数据结构,是由m(m>0)个有限节点组成的有层次关系的结合,树的结构中,子树之间是不能有交集的否则就是图,树是递归定义的。常见树的相关术语:⽗结点:若⼀个结点含有⼦结点&#xff0c…

2026/10/15 9:50:40 阅读更多 →
审计专业考研和考证怎么规划

审计专业考研和考证怎么规划

考研和考证,是审计专业学生绕不开的成长主题。很多人在大二大三就开始纠结:应该先全力准备考研,还是趁在校多拿几个证书?其实两者并非对立,如果能结合职业方向做一份清晰的规划,反而能相互借力。这篇文章梳…

2026/10/15 9:50:47 阅读更多 →
TegraRcmGUI 注入 payload 的 5 个关键问题,新手按顺序读完就能上手

TegraRcmGUI 注入 payload 的 5 个关键问题,新手按顺序读完就能上手

TegraRcmGUI 注入 payload 的 5 个关键问题,新手按顺序读完就能上手 【免费下载链接】TegraRcmGUI C GUI for TegraRcmSmash (Fuse Gele exploit for Nintendo Switch) 项目地址: https://gitcode.com/gh_mirrors/te/TegraRcmGUI 第一次进 RCM 的 Switch 是一…

2026/10/6 14:50:24 阅读更多 →

最新新闻

无支撑金属3D打印技术如何革新卫星制造

无支撑金属3D打印技术如何革新卫星制造

1. 项目概述:当太空基建遇上金属增材制造去年参观某航天器制造车间时,车间主任指着角落里一台正在工作的金属3D打印机说:"这玩意儿现在打的都是非承力件,要是能直接打印主结构件,我们的卫星产能至少翻三倍。"…

2026/10/15 9:51:48 阅读更多 →
Moodle部署到教学管理:从环境配置到避坑的完整实战指南

Moodle部署到教学管理:从环境配置到避坑的完整实战指南

简介:一份Moodle在线学习平台的使用说明文档,面向需要快速掌握课程资源建设与教学活动的教师、培训师及平台管理员,帮助解决在Moodle中创建和管理在线课程内容的核心问题。资源为单个doc文件,整体压缩包约9.02MB,内容编…

2026/10/15 9:51:48 阅读更多 →
HDS VSP Gx00存储安装配置指南:四个关键环节实操解析

HDS VSP Gx00存储安装配置指南:四个关键环节实操解析

简介:HDS VSP G200安装配置指南是一份面向存储管理员与系统工程师的实操型手册,聚焦Panama系列G200/G400/G600/G800统一搭载的SVOS系统,适合在设备开箱上架、控制框连线、SVP管理环境搭建等阶段按图索骥。资源包体积7.01MB,仅含1个…

2026/10/15 9:51:48 阅读更多 →
深信服翔鹰计划面试本质:安全产品工程师的能力建模

深信服翔鹰计划面试本质:安全产品工程师的能力建模

1. 这不是一场普通的技术面试:翔鹰计划背后的选拔逻辑“深信服翔鹰计划技术面试”——这八个字在应届生和初级工程师圈子里,近两三年逐渐从模糊的招聘词条,变成一个带着具体压力感的实操场景。我接触过不少参与过该流程的某高校学生和某实验室…

2026/10/15 9:51:48 阅读更多 →
Kettle循环批量同步Oracle到Elasticsearch实战:批次、循环与Job编排

Kettle循环批量同步Oracle到Elasticsearch实战:批次、循环与Job编排

1. 从Oracle到Elasticsearch:为什么批量同步这件事值得认真对待做过数据同步的人都有一个共识:把数据从关系型数据库搬到搜索引擎,听起来只是"读出来、写进去"两步,但真正落地的时候,坑几乎全在细节里。尤其…

2026/10/15 9:51:48 阅读更多 →
ASP.NET Core Cookie身份验证实现与安全配置指南

ASP.NET Core Cookie身份验证实现与安全配置指南

1. 身份验证基础与Cookie机制解析现代Web应用的身份验证主要有三种主流方式:Cookie认证、JWT令牌和OAuth协议。其中Cookie认证因其简单易用、浏览器原生支持的特性,成为ASP.NET Core中最常用的身份验证方案之一。Cookie本质上是一小段存储在客户端的文本…

2026/10/15 9:50:47 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →