数据库的死锁如何快速排查?

175 阅读4分钟

数据库死锁排查的核心是“定位死锁SQL + 分析锁竞争逻辑”,需结合数据库自带工具和日志快速定位,不同数据库(以MySQL、Oracle为例)排查流程如下:

一、通用排查逻辑:先“抓现场”,再“溯根源”

1.优先捕获死锁现场 死锁发生时会立即释放资源,需第一时间获取“谁在竞争锁、竞争什么锁”,避免现场丢失。

2.分析锁竞争链路 明确死锁的SQL语句、事务执行顺序,判断是“资源循环等待”(如A占锁1等锁2,B占锁2等锁1)还是“锁粒度不合理”(如用表锁导致大面积阻塞)。

3.验证并优化 复现死锁场景(可选),调整SQL、事务或锁策略,解决根本问题。

二、分数据库快速排查方案

1. MySQL 死锁排查(最常用)

MySQL 提供 show engine innodb status 和死锁日志,是排查核心工具。

步骤1:实时捕获死锁现场

执行以下命令,在输出结果中找到 “LATEST DETECTED DEADLOCK” 段落,该部分会显示最近一次死锁的完整信息:

show engine innodb status;

关键信息解读(需重点关注):

TRANSACTIONS FOR EACH SESSION:两个死锁事务的ID(如 TRANSACTION 12345);

WAITING FOR THIS LOCK TO BE GRANTED:事务等待的锁类型(如行锁 RECORD LOCKS、表锁 TABLE LOCKS)及关联的表、索引、行数据(如 index PRIMARY of table test.user);

HOLDS THE LOCK(S):事务已持有的锁;

SQL STATEMENT:触发死锁的具体SQL语句(如 update user set name='a' where id=1)。

步骤2:开启死锁日志持久化(长期监控)

若死锁偶发,可开启MySQL死锁日志,将死锁信息写入日志文件,便于后续分析:

1.临时开启(重启失效):

set global innodb_print_all_deadlocks = 1;

2.永久开启(修改配置文件 my.cnf):[mysqld]

innodb_print_all_deadlocks = 1
log-error = /var/log/mysql/error.log  # 日志路径,需确保权限

之后可通过查看日志文件获取历史死锁记录:

cat /var/log/mysql/error.log | grep -A 50 "DEADLOCK"

步骤3:辅助查看锁等待状态 若死锁未触发日志(或需实时监控锁等待),可执行以下SQL查看当前阻塞的事务和锁:

-- 查看所有事务及锁等待情况
select * from information_schema.innodb_trx;
-- 查看锁等待关系(谁阻塞了谁)
select * from information_schema.innodb_locks;
select * from information_schema.innodb_lock_waits;

2. Oracle 死锁排查

Oracle 死锁会自动记录到 告警日志(alert log),并可通过系统视图查询。

步骤1:通过视图快速定位死锁事务

执行以下SQL,找到死锁的会话ID(SID)、事务ID和对应的SQL:

-- 1. 查看死锁的会话和事务

select s.sid, s.serial#, t.xid, s.username, s.program
from v$session s
join v$transaction t on s.taddr = t.addr
where s.status = 'ACTIVE' and s.blocking_session is not null;

-- 2. 根据SID查询死锁SQL

select sql_text
from v$sql
where sql_id = (select sql_id from v$session where sid = 死锁SID);

步骤2:查看告警日志中的死锁详情

Oracle 死锁会自动写入告警日志,日志路径可通过以下命令查询:

show parameter background_dump_dest;

进入路径找到 alert_数据库名.log 文件,搜索关键词 “DEADLOCK DETECTED”,日志会显示死锁事务的锁类型(如 TX 行锁、TM 表锁)、竞争的资源(如表、行ID)。

三、死锁排查关键结论(快速定位原因)

1.锁类型判断:

  • 若为 行锁(InnoDB RECORD/TX):大概率是“事务1更新行A,事务2更新行B,随后事务1更新行B、事务2更新行A”导致循环等待;

  • 若为 表锁(MyISAM/InnoDB TM):可能是SQL未走索引(导致行锁升级为表锁),或显式使用 lock table。

2.SQL共性:

  • 死锁SQL通常是 更新/删除语句(UPDATE/DELETE),且筛选条件依赖同一索引(如都基于 user.id 更新)。

四、快速止损(死锁发生后)

1.优先 终止其中一个阻塞事务(通过事务ID/SID):

  • MySQL:kill 事务ID;(事务ID可从 innodb_trx.trx_id 获取);

  • Oracle:alter system kill session 'SID,serial#';(SID和serial#从 v$session 获取)。

2.后续优化方向:

  • 统一事务内SQL的执行顺序(如都先更进行A,再更新行B);
  • 缩小事务范围(避免事务内包含无关操作,减少锁持有时间);
  • 确保更新/删除语句走索引(避免行锁升级为表锁)。