mysql查询死锁,如何查询死锁、分析死锁日志

首页 2025-10-09 16:26:27


查询和处理 MySQL 死锁是数据库管理中的一项核心技能。下面我将为你详细讲解如何查询死锁、分析死锁日志,并提供一些避免死锁的最佳实践。

什么是死锁?

死锁(Deadlock)是指两个或多个事务在执行过程中,因争夺锁资源而造成的一种互相等待的僵局。当事务 A 持有锁 1 并等待锁 2,而事务 B 持有锁 2 并等待锁 1 时,就会发生死锁。此时,若无外部干预,两个事务都将永远无法继续执行。
MySQL 的 InnoDB 存储引擎会自动检测死锁,并通过回滚其中一个事务(通常是回滚代价较小的那个)来打破僵局。

如何查询死锁?

查询死锁主要有以下三种方法,从简单到深入。

方法一:查看最近一次死锁日志 (最常用)

MySQL 会记录最近一次发生的死锁详细信息。你可以通过执行以下命令来查看:
sql
SHOW ENGINE INNODB STATUS;
 
执行后,你会看到一个很长的状态报告。找到名为 LATEST DETECTED DEADLOCK 的部分,这里面包含了死锁的所有关键信息。
关键信息解读:
  • *** (1) TRANSACTION / *** (2) TRANSACTION: 参与死锁的两个事务。
  • UPDATE products SET stock = stock - 1 WHERE id = 1;: 每个事务正在执行的 SQL 语句。
  • *** (1) HOLDS THE LOCK(S): 事务 1 已经持有的锁。
  • *** (1) WAITING FOR THIS LOCK TO BE GRANTED: 事务 1 正在等待的锁。
  • *** WE ROLL BACK TRANSACTION 12345: MySQL 最终决定回滚事务 12345 来解决死锁。
通过分析这些信息,你就能清楚地看到两个事务是如何互相等待对方的锁的。

方法二:实时监控死锁 (适合长期观察)

如果你想持续监控死锁的发生,可以开启 MySQL 的死锁日志记录功能。
  1. 开启死锁日志在 MySQL 的配置文件 (my.cnf 或 my.ini) 的 [mysqld] 部分添加以下配置:
    ini
    [mysqld]
    innodb_print_all_deadlocks = 1
    
     
     
  2. 重启 MySQL 服务修改配置后,需要重启 MySQL 服务才能生效。
    bash
    sudo systemctl restart mysql
    
     
     
  3. 查看日志文件之后,所有发生的死锁都会被记录到 MySQL 的错误日志文件中。你可以通过以下命令找到错误日志的位置:
    sql
    SHOW VARIABLES LIKE 'log_error';
    
     
     
    然后使用 tailgrep 等命令来实时查看或检索死锁日志。
    bash
    tail -f /var/log/mysql/error.log | grep "DEADLOCK"
    
     
     

方法三:查询当前正在等待锁的事务 (间接排查)

如果死锁正在发生,你可以查询哪些事务正在等待锁,这有助于实时定位问题。
sql
SELECT 
    r.trx_id,
    r.trx_mysql_thread_id,
    r.trx_query,
    b.trx_id AS blocking_trx_id,
    b.trx_mysql_thread_id AS blocking_thread_id,
    b.trx_query AS blocking_query
FROM 
    information_schema.innodb_lock_waits w
JOIN 
    information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
JOIN 
    information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
 
这个查询会显示:
  • trx_id: 请求锁的事务 ID。
  • trx_query: 请求锁的事务正在执行的 SQL。
  • blocking_trx_id: 持有锁并造成阻塞的事务 ID。
  • blocking_query: 持有锁的事务正在执行的 SQL。

如何处理和避免死锁?

查询到死锁只是第一步,更重要的是解决它。

1. 手动处理(紧急情况)

如果发现某个事务长期阻塞,可以手动终止它。
  1. 找到阻塞的线程 ID从上面的 SHOW ENGINE INNODB STATUS 或 information_schema 查询中找到造成阻塞的 MySQL thread id
  2. 终止线程
    sql
    -- 假设阻塞线程的 ID 是 11
    KILL 11;
    
     
     
    注意:这是一个高危操作,会强制回滚该线程正在执行的所有未提交事务,请谨慎使用。

2. 优化应用逻辑(根本解决)

从根本上避免死锁是最佳策略。以下是一些经过实践检验的最佳实践:
  • 保持一致的锁顺序:这是最重要的原则。在所有事务中,确保以相同的顺序来获取锁(即更新表和行的顺序)。例如,在处理订单和库存时,所有事务都先更新 orders 表,再更新 products 表。
  • 尽量缩小事务范围:让事务尽可能地短,减少持有锁的时间。避免在事务中执行耗时的操作,如等待用户输入、调用外部 API 等。
  • 使用更低级别的锁:如果业务允许,使用 SELECT ... FOR UPDATE (行级锁) 代替 LOCK TABLES (表级锁)。行级锁的粒度更细,冲突概率更低。
  • 为查询添加合适的索引:没有索引的 UPDATE 或 DELETE 语句可能会导致 MySQL 扫描全表并锁定所有行,大大增加死锁风险。确保 WHERE 子句中的条件列都有索引。
  • 设置合理的事务隔离级别:大多数应用使用默认的 REPEATABLE READ 隔离级别。在某些情况下,降低到 READ COMMITTED 可以减少 Gap Lock(间隙锁)的范围,从而降低死锁概率。
  • 使用 SELECT ... FOR UPDATE SKIP LOCKED (MySQL 8.0+):在高并发场景下,如果一个事务只是想获取一个可用的资源(如从队列中取任务),可以使用 SKIP LOCKED。它会跳过那些已经被锁定的行,而不是等待,从而从根本上避免了死锁。

总结

  1. :使用 SHOW ENGINE INNODB STATUS; 查看最近一次死锁的详细信息。
  2. :分析死锁日志,找出是哪两个(或多个)事务、哪两条 SQL、争夺了哪些锁。
    • 紧急处理:使用 KILL 命令终止阻塞的事务。
    • 长期优化:遵循保持一致的锁顺序等最佳实践,从根本上消除死锁产生的条件。
通过以上方法,你就可以有效地查询、分析和解决 MySQL 中的死锁问题了。
MySQL连接就这么简单!本地远程、编程语言连接方法一网打尽
还在为MySQL日期计算头疼?这份加一天操作指南能解决90%问题
MySQL日志到底在哪里?Linux/Windows/macOS全平台查找方法在此
MySQL数据库管理工具全景评测:从Workbench到DBeaver的技术选型指南
MySQL密码忘了怎么办?这份重置指南能救急,Windows/Linux/Mac都适用
你的MySQL为什么经常卡死?可能是锁表在作怪!快速排查方法在此
MySQL单表卡爆怎么办?从策略到实战,一文掌握「分表」救命技巧
清空MySQL数据表千万别用错!DELETE和TRUNCATE这个区别可能导致重大事故
你的MySQL中文排序一团糟?记住这几点,轻松实现准确拼音排序!
别再混淆Hive和MySQL了!读懂它们的天壤之别,才算摸到大数据的门道