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

MySQL 死锁问题分析与解决方案


****


一、死锁原因分析

死锁通常由以下场景引发:

  1. 事务执行顺序不一致:多个事务以不同顺序访问相同资源。
  2. 索引缺失:全表扫描导致行锁升级为表锁。
  3. 长事务或大事务:长时间持有锁资源,增加冲突概率。
  4. 隔离级别设置:如 REPEATABLE READ 隔离级别下的间隙锁竞争。

二、诊断死锁
1. 查看死锁日志
SHOW ENGINE INNODB STATUS;  -- 获取最新死锁信息

重点关注 LATEST DETECTED DEADLOCK 段,分析涉及的事务、SQL 及锁信息。

2. 开启死锁监控(长期跟踪)
# my.cnf 配置
innodb_print_all_deadlocks = ON  -- 记录所有死锁到错误日志

三、解决死锁的常见方法
1. 优化事务逻辑
  • 固定资源访问顺序:确保所有事务按相同顺序操作表或行。

    -- 事务1和事务2均按顺序更新表A、表B
    BEGIN;
    UPDATE table_a SET ... WHERE id = 1;
    UPDATE table_b SET ... WHERE id = 2;
    COMMIT;
    
  • 缩短事务时间:避免在事务中执行耗时操作(如外部 API 调用)。

2. 索引优化
  • WHEREJOINORDER BY 条件字段添加索引,减少锁范围。
    -- 添加联合索引减少锁冲突
    ALTER TABLE orders ADD INDEX idx_user_product (user_id, product_id);
    
3. 降低锁粒度
  • 使用行锁代替表锁:确保操作通过索引定位数据。
  • 避免 SELECT ... FOR UPDATE 滥用:仅在必要时加锁。
4. 调整隔离级别
  • 使用 READ COMMITTED 隔离级别,减少间隙锁(Gap Lock)的使用:
    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
    
5. 主动死锁检测与重试

在代码层捕获死锁错误(错误码 1213),自动重试事务:

# Python 示例(伪代码)
max_retries = 3
for attempt in range(max_retries):
    try:
        with connection.cursor() as cursor:
            cursor.execute("BEGIN")
            # 执行SQL操作
            cursor.execute("COMMIT")
        break
    except pymysql.err.OperationalError as e:
        if e.args[0] == 1213:  # 死锁错误码
            connection.rollback()
            time.sleep(0.1 * (2 ** attempt))  # 指数退避
        else:
            raise

四、预防死锁的最佳实践
  1. 事务设计原则

    • 保持事务简短,尽快提交或回滚。
    • 避免在事务中执行用户交互操作。
  2. 索引与查询优化

    • 定期分析慢查询日志,优化全表扫描语句。
    • 使用 EXPLAIN 检查 SQL 执行计划。
  3. 监控与告警

    • 通过 Prometheus + Grafana 监控死锁频率。
    • 配置报警规则(如每分钟死锁数超过阈值)。

五、案例分析
场景描述

两个并发事务引发死锁:

  • 事务1UPDATE table SET ... WHERE id = 1;UPDATE table SET ... WHERE id = 2;
  • 事务2UPDATE table SET ... WHERE id = 2;UPDATE table SET ... WHERE id = 1;
死锁日志解读
LATEST DETECTED DEADLOCK
...
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 0 sec updating
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 100, OS thread handle 0x7f8a1c0d6700, query id 2000 updating
UPDATE table SET ... WHERE id = 1;

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 10 n bits 80 index PRIMARY of table `test`.`table`
 trx id 12345 lock_mode X locks rec but not gap

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 11 n bits 80 index PRIMARY of table `test`.`table`
 trx id 12345 lock_mode X locks rec but not gap

*** (2) TRANSACTION:
TRANSACTION 67890, ACTIVE 0 sec updating
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 101, OS thread handle 0x7f8a1c0d6800, query id 2001 updating
UPDATE table SET ... WHERE id = 2;
...
解决方案
  • 统一更新顺序:所有事务按 id 升序更新。
  • 合并更新语句:使用单条 SQL 更新多行。
    UPDATE table SET ... WHERE id IN (1, 2) ORDER BY id ASC;
    

六、工具推荐
  1. pt-deadlock-logger(Percona Toolkit):

    pt-deadlock-logger --ask-pass --host=localhost --user=root
    

    实时监控死锁事件并记录到文件。

  2. 性能模式(Performance Schema)

    SELECT * FROM performance_schema.data_locks;  -- 查看当前锁状态
    

总结

解决 MySQL 死锁需结合 事务逻辑优化索引调整锁机制理解,核心原则是减少资源竞争。通过监控工具快速定位问题,并在代码层实现重试机制,可显著降低死锁对业务的影响。

相关文章:

  • 机房布局和布线的最佳实践:如何打造高效、安全的机房环境
  • fopen和open 等区别是什么?文件描述符与文件描述指针区别
  • 如何防御大模型中的 Prompt 攻击?
  • 跨境电商独立站B端站与C端站有什么不同
  • stress-ng命令详解
  • 在线文档导出为word/pdf/png
  • 瑞萨RX23E系列开发(二)建立工程
  • 【VUE】day06 动态组件 插槽 自定义指令 ESlint
  • 用 pytorch 从零开始创建大语言模型(五):预训练无标注数据
  • 【网络层协议】NAT技术内网穿透
  • 复变函数摘记2
  • 蓝桥备赛指南(8):01背包模型
  • WPS宏开发手册——JSA语法
  • 在Linux、Windows系统上安装开源InfluxDB——InfluxDB OSS v2并设置开机自启的保姆级图文教程
  • TCP协议原理
  • CentOS7 离线下载安装 GitLab CE
  • ubuntu设置开机自动运行应用
  • UE5摄像机震屏/晃动效果
  • 银河麒麟桌面版包管理器(四)
  • Xshell、Xsftp、Xmanager中文版安装包及使用教程
  • AG600“鲲龙”批生产首架机完成生产试飞
  • 国际乒联主席索林:洛杉矶奥运会增设混团是里程碑事件
  • 官方通报汕头违建豪宅“英之园”将强拆:对有关人员严肃追责问责
  • 美联储官员:美国经济增速可能放缓,现行关税政策仍将导致物价上涨
  • 马上评|训斥打骂女儿致死,无暴力应是“管教”底线
  • 美官方将使用华为芯片视作违反美出口管制行为,外交部回应