死锁是事务间形成等待环:每个事务等待另一个事务持有的锁,单纯等待无法自行推进。InnoDB 通常检测到环后选择一个事务作为牺牲者回滚;应用仍需捕获死锁错误,并在事务边界安全重试。先区分死锁和普通锁等待超时:超时表示等待超过配置期限,不一定存在环;扩大 timeout 不能消除死锁。
可用两个连接复现:A 先锁 id=1 再请求 id=2;B 先锁 id=2 再请求 id=1。当双方各持有一个锁并等待对方时,就形成环。常见根因是事务按不同顺序更新多行、事务过长或索引不足导致扫描锁范围扩大。
第一步查看最近死锁详情:
SHOW ENGINE INNODB STATUS\G在 LATEST DETECTED DEADLOCK 中检查两个事务的 SQL、锁模式、等待索引记录、被回滚事务及索引名。该区域通常只保留最近一次死锁,事件发生后应尽快采集;必要时评估 innodb_print_all_deadlocks 将死锁记录写入错误日志。SHOW ENGINE 输出需结合当时事务状态,不能只看最终报错 SQL。
等待中的锁可从 MySQL 8.0 Performance Schema 关联查看(需要有相应权限):
SELECT w.REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx, rt.PROCESSLIST_ID AS waiting_pid, r.OBJECT_SCHEMA, r.OBJECT_NAME, r.INDEX_NAME, r.LOCK_MODE AS waiting_mode, r.LOCK_DATA AS waiting_data, w.BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx, bt.PROCESSLIST_ID AS blocking_pid, b.LOCK_MODE AS blocking_mode, b.LOCK_DATA AS blocking_dataFROM performance_schema.data_lock_waits AS wJOIN performance_schema.data_locks AS r ON r.ENGINE_LOCK_ID = w.REQUESTING_ENGINE_LOCK_IDJOIN performance_schema.data_locks AS b ON b.ENGINE_LOCK_ID = w.BLOCKING_ENGINE_LOCK_IDLEFT JOIN performance_schema.threads AS rt ON rt.THREAD_ID = r.THREAD_IDLEFT JOIN performance_schema.threads AS bt ON bt.THREAD_ID = b.THREAD_ID;data_lock_waits 描述请求锁与阻塞锁关系,data_locks 展示锁对象和模式;这反映当前等待,不是已结束死锁的历史档案。诊断流程是保存现场、识别等待环和 SQL、检查索引与锁范围、统一多行访问顺序并缩短事务,然后压测验证。不要不加判断地 KILL 连接;终止连接会触发回滚且可能扩大业务影响。
追问
SHOW ENGINE INNODB STATUS 为什么查不到刚才的死锁?
它主要显示最近检测到的死锁,后续死锁可能覆盖记录;应及时采集,或评估记录所有死锁到错误日志。
data_lock_waits 能否查询过去的死锁?
不能作为历史审计表;它用于观察当前锁等待。历史现场需依赖 InnoDB 状态输出、错误日志或外部监控采集。
代码怎么降低死锁概率?
让事务按统一顺序访问多行、缩短事务、为过滤条件建立合适索引,并对死锁受害事务做有限且幂等的重试。