SHOW ENGINE INNODB STATUS 的死锁段落不直接导致死锁,真正触发者是其内部执行的SQL(如UPDATE、DELETE、SELECT...FOR UPDATE);需第一时间查看LATEST DETECTED DEADLOCK区域,获取事务ID、线程ID、持有锁、等待锁及被回滚事务信息,并结合PROCESSLIST和应用日志交叉验证是否源于存储过程。

直接看 SHOW ENGINE INNODB STATUS 的死锁段落
存储过程本身不“导致”死锁,真正触发死锁的是它内部执行的 SQL 语句(尤其是 UPDATE、DELETE、SELECT ... FOR UPDATE)。所以排查起点和普通事务完全一致:必须第一时间拿到 InnoDB 检测到的最后一次死锁快照。
执行 SHOW ENGINE INNODB STATUS 后,重点找 LATEST DETECTED DEADLOCK 区域。这里会明确写出两个冲突事务各自的:
-
TRANSACTIONID 和 MySQL 线程 ID(thread id) - 各自持有的锁(
HOLDS THE LOCK(S)),包括索引名、锁模式(X locks rec but not gap还是lock_mode X locks gap before rec) - 各自等待的锁(
WAITING FOR THIS LOCK TO BE GRANTED)及对应 SQL - 被回滚的是哪个事务(
WE ROLL BACK TRANSACTION (1))
⚠️ 注意:如果存储过程里有多个 SQL,WAITING FOR 显示的那条 SQL 就是死锁发生时正在执行的语句,不是开头第一条 —— 别凭印象猜。
确认死锁 SQL 是否来自存储过程
仅靠 SHOW ENGINE INNODB STATUS 无法直接看出某条 SQL 是不是在存储过程中执行的,因为输出里只显示原始 SQL 文本,不带调用栈。你需要交叉验证:
- 查
information_schema.PROCESSLIST,过滤出死锁中提到的thread id,看INFO列是否为空或只显示CALL proc_name(...)—— 如果是,说明该线程正在执行存储过程 - 查应用日志,匹配同一时间点、同一数据库连接 ID(
thread_id)的调用记录,看是否对应某个CALL语句 - 在存储过程中加日志(如写入临时表或调用
SIGNAL抛出自定义 warning),定位具体执行到哪一步时卡住(需提前埋点)
常见陷阱:CALL my_proc(1,2) 在 PROCESSLIST 中可能只显示为 CALL,而后续的 UPDATE 语句不会单独出现在 INFO 列 —— 这就是为什么必须结合线程 ID 和时间戳对齐。
存储过程特有的死锁诱因
相比普通事务,存储过程更容易放大死锁风险,原因集中在控制流和隐式行为上:
-
循环内重复加锁顺序不一致:比如一个循环处理订单列表,但每次
UPDATE的WHERE条件未按主键排序,导致不同调用间锁行顺序随机 -
条件分支导致锁范围突变:例如
IF @status = 'pending' THEN UPDATE ... WHERE id = ? AND status = 'pending'; ELSE UPDATE ... WHERE id = ?;—— 两条路径走的索引或锁类型可能完全不同 -
隐式事务边界模糊:MySQL 存储过程中默认不自动开启事务,但如果开启了
AUTOCOMMIT=0或显式写了START TRANSACTION,而忘记COMMIT,会导致锁长期持有 -
游标遍历 + 行更新混合:游标
FETCH本身不加锁,但后续UPDATE current of cursor(如果支持)或基于游标结果再查再更新,极易形成非预期锁顺序
性能影响:这类逻辑一旦出问题,不是单次失败,而是高并发下批量触发死锁,错误率会随 QPS 非线性上升。
验证与修复的关键检查点
不要只改 SQL,要验证整个存储过程的锁行为是否可控:
- 用
EXPLAIN FORMAT=TRADITIONAL检查每条 DML 语句是否命中预期索引,避免全表扫描导致锁扩大 - 确保所有涉及更新的
WHERE条件字段都有合适索引,特别是组合条件中的前导列 - 若业务允许,把长存储过程拆成多个短事务,或在循环外预取 ID 列表并排序(如
ORDER BY id ASC)再逐条处理 - 在测试环境用
innodb_print_all_deadlocks = ON持续捕获死锁日志,比依赖单次INNODB STATUS更可靠
最容易被忽略的一点:存储过程里的变量赋值(如 SELECT @x := col FROM t WHERE ...)虽不加锁,但如果和后续的 UPDATE 共享相同条件,而该查询没走索引,就会先扫全表再更新 —— 扫描过程本身会加间隙锁,为死锁埋雷。


















