MySQL锁等待与死锁:1205、1213排查

订单保存一直转圈,最后返回 1205;或者更新数据立即报 1213,这两种情况都与锁有关,处理办法却不同。本文适用于MySQL 8.0、8.4 的 InnoDB 表,由有权诊断实例的管理员执行。MariaDB、MySQL 5.7 不直接套用这里的视图查询。

错误 发生了什么 应用要注意什么
1205:Lock wait timeout 等待锁超过限制。 InnoDB 默认只撤销超时的那条语句,事务前面执行的操作不一定已回滚。
1213:Deadlock found 事务互相等待,InnoDB 选择其中一个事务作为受害者。 受害事务已整体回滚,需要按完整事务处理重试。

先确认报错来自哪个实例

使用现有的管理连接登录 MySQL,记录应用报错时间和接口名称,再执行下面的只读查询。密码不要写进命令或截图。

SELECT VERSION() AS mysql_version,
       @@version_comment AS distribution,
       @@hostname AS server_name,
       @@port AS server_port,
       @@global.innodb_rollback_on_timeout AS rollback_on_timeout;

核对版本、机器和端口,避免查了另一台数据库。rollback_on_timeout=1表示启用了超时回滚整个事务的选项;为 0 时不能把 1205 当成整个事务已经撤销。查询无权限时交给具备相应权限的管理员,不为排查给业务账号开放全部权限。

1205还在发生时,找谁在等谁

在出现卡住的同一时间检查当前 InnoDB 锁等待:

SELECT wait_age_secs,
       waiting_pid, blocking_pid,
       waiting_query, blocking_query
FROM sys.innodb_lock_waits
ORDER BY wait_age_secs DESC
LIMIT 20;

waiting_pid是等待方,blocking_pid是阻塞它的连接;这两个值是 MySQL 连接 ID,不是 Linux 进程号。优先看等待时间长、同一个阻塞方反复出现的记录。blocking_query为空也不能排除阻塞:连接可能已经空闲,但事务还没提交。

接着查事务,把 connection_id与前面的阻塞连接对应起来:

SELECT trx_mysql_thread_id AS connection_id,
       trx_state, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS trx_seconds,
       trx_rows_locked, trx_rows_modified, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started
LIMIT 20;

长时间未结束、已经改动过数据的事务值得优先追查。确认它属于哪个接口、批处理或人工 SQL 窗口,不能只看“运行时间长”就终止。这里显示的是当前状态,查询之间也可能发生变化。

如果执行的是 ALTER TABLE等操作,或会话显示 Waiting for table metadata lock,再看表的元数据锁:

SELECT object_schema, object_name,
       waiting_pid, blocking_pid,
       waiting_query_secs, waiting_query
FROM sys.schema_table_lock_waits
ORDER BY waiting_query_secs DESC
LIMIT 20;

元数据锁与 InnoDB 行锁是不同层次;调大 innodb_lock_wait_timeout不能解决所有等待。上述视图依赖 sys、Performance Schema 和相应采集;视图不存在、权限不足或采集关闭,需要先核对环境。结果为空只说明这次没有捕捉到记录,不证明刚才没有锁问题。

1213先看最近一次死锁

SHOW ENGINE INNODB STATUS;

在输出中找到 LATEST DETECTED DEADLOCK,核对时间、双方 SQL、涉及索引及回滚对象。它通常只保留最近一次死锁,时间对不上本次报错就不能直接归因。死锁受害者已经回滚,因此事后查等待视图,常常是空的。

问题持续发生时,管理员可安排短时开启 innodb_print_all_deadlocks采集完整记录,结束后恢复原设置。日志可能包含 SQL 和业务字段,提交协助前先脱敏。

解除这次阻塞,再修应用事务

先联系持锁业务的维护人,确认该事务应当完成还是撤销。提交或回滚要在原来的业务连接上处理,另开一个 SQL 窗口执行 ROLLBACK 不会结束别人的事务。

不要直接复制工具生成的 KILL 语句。KILL QUERY只结束正在执行的语句,保留连接,不能保证把它的整个事务和锁都释放;终止连接又可能触发耗时回滚。确需中断时,由管理员核对连接归属、业务影响和回滚进度,不通过重启整个 MySQL 清场。

  • 缩短事务:远程接口调用、人工等待和耗时文件处理尽量放在事务外。
  • 统一更新顺序:多个业务更新同一组表或记录时,按一致顺序取得锁。
  • 检查索引与扫描范围:先看执行计划,减少本来只改少量记录却扫描大量索引记录的情况,参照慢 SQL 排查。
  • 让应用正确收尾和重试:1213 按完整事务做有次数限制的重试;1205 先明确事务状态,再按业务逻辑回滚或继续。支付、发信等外部操作还要防止重复执行。

新增索引或改表前先完成备份与恢复验证,不要在故障高峰临时大改结构。单纯增加等待超时,往往只是让请求排队更久。

怎样算处理完了

在相近业务负载下重新检查:阻塞事务是否结束、等待记录是否持续回落、接口耗时及 1205/1213 次数是否下降,同时核对订单或库存的最终状态。偶发死锁被应用正确重试,与持续失败导致业务无法完成,应分别判断。

仍需协助时,提供故障时间、MySQL 版本、等待与阻塞连接 ID、脱敏后的 SQL 和最近死锁记录。连接本身就失败的情况,先看MySQL 连接错误排查。