当前位置: 首页 > web >正文

【MySQL系列】数据库死锁问题

csdn

博客目录

    • 引言:死锁现象及其影响
    • 一、死锁原理与 MySQL 死锁机制
      • 1.1 什么是数据库死锁
      • 1.2 MySQL 的死锁检测机制
      • 1.3 死锁的四个必要条件
    • 二、案例深度分析
      • 2.1 错误场景还原
      • 2.2 为什么查询会触发更新?
      • 2.3 SQLAlchemy 的 autoflush 机制
    • 三、解决方案与优化建议
      • 3.1 短期解决方案
      • 3.2 长期优化策略
        • 3.2.1 事务设计优化
        • 3.2.2 数据库设计优化
        • 3.2.3 应用程序优化
      • 3.3 业务逻辑重构建议
    • 四、死锁排查与诊断技巧
      • 4.1 MySQL 死锁日志分析
      • 4.2 使用 SHOW ENGINE INNODB STATUS
      • 4.3 性能模式(Performance Schema)监控
    • 五、预防死锁的最佳实践
    • 六、特定于 ORM 的优化建议

引言:死锁现象及其影响

在现代 Web 应用开发中,数据库死锁(Deadlock)是开发人员经常遇到的一个棘手问题。最近在一个使用 SQLAlchemy 和 MySQL 的项目中,我们遇到了一个典型的死锁错误:sqlalchemy.exc.OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')。这个错误发生在调用TaskService.get_task_by_id()方法时,系统尝试自动刷新(autoflush)并执行UPDATE user SET remaining_times=998283 WHERE user.id = 11语句的过程中。

一、死锁原理与 MySQL 死锁机制

1.1 什么是数据库死锁

死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,这些事务都无法继续执行下去。在我们的案例中,当多个事务同时尝试修改用户 ID 为 11 的记录时,MySQL 检测到了这种循环等待的情况,于是主动中断了其中一个事务。

1.2 MySQL 的死锁检测机制

MySQL 的 InnoDB 存储引擎具有内置的死锁检测机制。当检测到死锁时,它会选择代价较小的事务(通常影响行数较少的事务)进行回滚,并返回 1213 错误码。这种机制虽然防止了系统陷入无限等待,但也给应用程序带来了需要处理的异常情况。

1.3 死锁的四个必要条件

理解死锁产生的四个必要条件有助于我们预防死锁:

  1. 互斥条件:资源一次只能被一个事务占用
  2. 请求与保持条件:事务在持有资源的同时请求新资源
  3. 不剥夺条件:已分配的资源不能被其他事务强行夺取
  4. 循环等待条件:多个事务形成头尾相接的等待关系
    在这里插入图片描述

二、案例深度分析

2.1 错误场景还原

在我们的具体案例中,死锁发生在以下操作序列中:

  1. 应用程序调用TaskService.get_task_by_id()方法
  2. SQLAlchemy 触发自动刷新机制(autoflush)
  3. 系统尝试执行用户表更新:UPDATE user SET remaining_times=998283 WHERE user.id = 11
  4. 此时该记录已被其他事务锁定,形成资源竞争

2.2 为什么查询会触发更新?

这是本案例中一个值得深思的问题。表面上是在查询任务信息,实际上却触发了用户表的更新。可能的原因包括:

  • 业务逻辑中设置了级联操作(Cascade)
  • 存在数据库触发器(Trigger)
  • ORM 中配置了关系加载策略(Relationship Load)
  • 业务代码中隐式包含了状态更新

2.3 SQLAlchemy 的 autoflush 机制

SQLAlchemy 的 autoflush 是一个方便但可能带来意外的特性。在执行查询前,它会自动将所有挂起的变更刷新到数据库。这种自动化虽然简化了开发,但在高并发场景下可能引发意料之外的事务冲突。

三、解决方案与优化建议

3.1 短期解决方案

对于眼前的死锁问题,最直接的解决方法是禁用 autoflush:

with session.no_autoflush():task = TaskService.get_task_by_id(task_id=task_id)

这种方法虽然简单有效,但只是治标不治本,我们需要更系统的解决方案。

3.2 长期优化策略

3.2.1 事务设计优化
  • 缩短事务范围:确保事务尽可能短小精悍,尽快提交
  • 统一访问顺序:对多个资源的访问保持一致的顺序,避免交叉请求
  • 合理设置隔离级别:根据业务需求选择合适的事务隔离级别
3.2.2 数据库设计优化
  • 添加适当索引:特别是对高频更新的字段建立合适索引
  • 考虑垂直拆分:将高频更新字段分离到单独表
  • 优化表结构:避免宽表和过多的索引
3.2.3 应用程序优化
  • 实现重试机制:对死锁错误实现自动重试(3-5 次为宜)
  • 缓存热点数据:对remaining_times等高频率更新数据使用缓存
  • 批量操作:将多个小更新合并为批量操作

3.3 业务逻辑重构建议

  1. 解耦查询与更新:明确区分只读操作和写操作
  2. 重新评估remaining_times设计
    • 是否可以考虑乐观锁替代悲观锁
    • 是否可以使用原子操作(如UPDATE user SET remaining_times=remaining_times-1
  3. 引入消息队列:对非实时性要求高的更新操作采用异步处理

四、死锁排查与诊断技巧

4.1 MySQL 死锁日志分析

MySQL 提供了死锁日志记录功能,通过以下命令开启:

SET GLOBAL innodb_print_all_deadlocks = ON;

分析死锁日志可以清楚地看到:

  • 涉及的事务和 SQL 语句
  • 每个事务持有的锁和等待的锁
  • 被选为牺牲品(victim)的事务

4.2 使用 SHOW ENGINE INNODB STATUS

这个命令可以提供详细的 InnoDB 状态信息,包括最近的死锁信息。关键信息包括:

  • LATEST DETECTED DEADLOCK部分显示最近死锁详情
  • 事务的等待关系图
  • 涉及的索引和记录

4.3 性能模式(Performance Schema)监控

MySQL 5.6+的 Performance Schema 提供了更强大的监控能力:

SELECT * FROM performance_schema.events_waits_current;
SELECT * FROM performance_schema.data_locks;

五、预防死锁的最佳实践

  1. 统一资源访问顺序:确保所有事务以相同顺序访问表和行
  2. 合理设计索引:良好的索引可以减少锁的粒度和持有时间
  3. 避免长事务:长时间运行的事务增加了死锁概率
  4. 使用乐观锁:对适合的场景使用版本号或时间戳控制
  5. 设置锁等待超时innodb_lock_wait_timeout可以控制等待时间
  6. 读写分离:将报表类查询转移到只读副本

六、特定于 ORM 的优化建议

针对 SQLAlchemy 等 ORM 框架,还有一些特定建议:

  1. 谨慎使用 autoflush:在复杂操作中手动控制 flush 时机
  2. 优化会话管理
    • 避免长时间保持会话打开
    • 考虑使用expire_on_commit=False减少延迟加载的锁竞争
  3. 批量操作优化
    session.bulk_save_objects()
    session.bulk_update_mappings()
    
  4. 合理配置关系加载
    • 使用lazy='select'而非lazy='joined'减少连接
    • 考虑raiseload避免意外查询

觉得有用的话点个赞 👍🏻 呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

img

http://www.xdnf.cn/news/8932.html

相关文章:

  • TDK PC95铁氧体隔磁片的技术要求
  • uniapp中懒加载图片组件的封装与应用
  • 【Qt】QCustomPlot相关
  • 网络段、主机段、子网掩码
  • Python 学习日记 day26
  • 蓝桥杯178 全球变暖
  • 【深度解读】三一重工的数字化转型(下篇2)
  • 大数据学习(118)-SQL面试问题总结
  • @Valid和@Vlidated的区别
  • Windows安装Docker Desktop开启 Kubenetes制作并部署本地镜像
  • Java 装饰器模式(Decorator)详解​
  • AI练习:指纹
  • [C语言实战]C语言文件操作实战:打造高效日志系统(六)
  • RMAN恢复报错RMAN-06555及其解决方案
  • STM32F103_Bootloader程序开发02 - Bootloader程序架构与STM32F103ZET6的Flash内存规划
  • idea和cursor快速切换
  • 【Linux】定时任务 Crontab 与时间同步服务器
  • 基于多头注意力时间卷积网络(MATCN)的虚拟电厂短期功率预测模型
  • 『uniapp』自己实现手动图片列表滑动 + 图片手势缩放+ 图片点击缩放(详细图文注释)
  • 分布式消息中间件设计与实现
  • Android自定义View学习总结
  • 【机器人】复现 Embodied-Reasoner 具身推理 | 具身任务 深度推理模型 多模态场景 长远决策 多轮互动
  • Python Day33
  • GO 语言中变量的声明
  • Python中字典(dict)知识详解应用
  • 非接触式互连:当串扰是您的朋友时
  • NumPy 数组属性
  • 英语科研词汇现象及语言演变探讨
  • Rephrase and Respond :让大语言模型为自己提出更优的问题
  • Disruptor—3.核心源码实现分析二