bigywater.tech
常见报错 · 2

MySQL ERROR 1205 Lock wait timeout exceeded:默认只回滚超时那一条语句,改别的行也可能被堵

看到 “Lock wait timeout exceeded; try restarting transaction” 时,调大 innodb_lock_wait_timeout 往往没用:真正的问题是谁在堵、堵了多久、超时之后到底回滚了什么。这篇用本机的 MySQL 把 1205 复现出来,逐个看这几个问题,再对比它和死锁 1213 的区别。

2.01 s
B 会话改被 A 锁住的同一行,在 innodb_lock_wait_timeout = 2 时等到报 1205 的时间(默认值是 50 秒)
7 / 0
超时之后 B 事务里更早的那条更新:默认设置下提交后是 7(还在),innodb_rollback_on_timeout=ON 时是 0(整个回滚)
6 / 3
WHERE name = a 时 UPDATE 持有的记录锁个数:name 没有索引是 6 个(整张表),有索引是 3 个

复现 1205

实测 · 本机执行输出
  默认值 @@innodb_lock_wait_timeout = 50 秒;下面把 B 会话的值改成 2 秒,免得等 50 秒
  B: update t set v = v + 10 where id = 1 -> OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction'),等了 2.01 s
  同一个 B 会话改第 3 行 -> ok,用了 0.9 ms(A 没碰这一行)

A 改一行不提交,B 改同一行,等满 innodb_lock_wait_timeout 报 1205。同一个会话改别的行立即成功,说明堵的只是争同一把行锁的语句。

超时回滚了什么

实测 · 本机执行输出
  默认(innodb_rollback_on_timeout=OFF):B 的第二条超时 -> OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction')(等了 2.00 s)
    B 超时之后在 B 自己会话里查第 2 行(@@innodb_rollback_on_timeout, v): (0, 7)
    B 提交之后,别的会话看第 2 行的 v = 7
  --innodb-rollback-on-timeout=ON:B 的第二条超时 -> OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction')(等了 2.01 s)
    B 超时之后在 B 自己会话里查第 2 行(@@innodb_rollback_on_timeout, v): (1, 0)
    B 提交之后,别的会话看第 2 行的 v = 0

默认只回滚超时的那一条语句,事务保持打开,前面成功的语句还在;打开 innodb_rollback_on_timeout 后整个事务回滚。本篇是用启动选项打开它的。默认设置下应用必须在捕获 1205 后自己决定是否回滚整个事务。

谁在堵

实测 · 本机执行输出
  waiting_pid        21
  waiting_query      update t set v = v + 10 where id = 1
  blocking_pid       20
  blocking_query     None
  locked_table       `d`.`t`
  locked_index       PRIMARY
  waiting_lock_mode  X,REC_NOT_GAP
  blocking_lock_mode X,REC_NOT_GAP
  wait_age_secs      2
  堵人的会话(processlist: id, command, 空闲秒, state, info): (20, 'Sleep', 2, '', None)
  堵人的事务(innodb_trx: trx_state, 已开始, 锁住的行数, trx_query): ('RUNNING', 1, 1, None)
  KILL 20 之后,等的人的结果:ok,等了 1.52 s(超时设的是 20 s,中途就被放行了)
  被 KILL 的 A 再发语句: OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")
  第 1 行的 v = 10 (A 的更新被回滚了,只有 B 的 +10)

sys.innodb_lock_waits 给出等的人和堵的人。堵的人经常是一个 Sleep 状态、没提交的事务,它的 trx_query 是空的——语句早执行完了,只是事务没结束。KILL 它之后等的人立刻继续。

无索引的 WHERE 锁全表

实测 · 本机执行输出
  t2(name 没有索引):A 改的是 name = a 那一行,performance_schema.data_locks 里 t2 的记录锁 6 个;B 改 id = 5 -> OperationalError(用了 2.01 s)
      t2 的锁:index=PRIMARY mode=X data=1
      t2 的锁:index=PRIMARY mode=X data=2
      t2 的锁:index=PRIMARY mode=X data=3
      t2 的锁:index=PRIMARY mode=X data=4
      t2 的锁:index=PRIMARY mode=X data=5
      t2 的锁:index=PRIMARY mode=X data=supremum pseudo-record
  t3(name 有索引 idx_name):A 改的是 name = a 那一行,performance_schema.data_locks 里 t3 的记录锁 3 个;B 改 id = 5 -> ok(用了 0.01 s)
      t3 的锁:index=idx_name mode=X data='a', 1
      t3 的锁:index=idx_name mode=X,GAP data='b', 2
      t3 的锁:index=PRIMARY mode=X,REC_NOT_GAP data=1
  (默认隔离级别 REPEATABLE READ。没有索引时,扫描过的每一行都加了锁;有索引只锁命中的那一行和附近的间隙)

REPEATABLE READ 下,没有索引可用时 UPDATE 扫过的每一行都被加锁。给过滤列建索引,持有的锁个数从 6 个降到 3 个,别的行不再被堵。

1205 与 1213

实测 · 本机执行输出
  这一节两个会话的 innodb_lock_wait_timeout 都是默认的 50 秒
  A 先改 1 再改 2,B 先改 2 再改 1:A 第二条 -> ok;B 第二条 -> OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
  B 的第二条语句从发出到收到 1213 用了 1.1 ms(远小于 50 s)
  对照:第一节同样的两个会话争一行,没有环,只有等到 innodb_lock_wait_timeout 才报 1205(那一节把它设成了 2 秒,等了约 2 s)

死锁是一个环,InnoDB 立刻发现并回滚一方(这次 1.1 ms);没有环的单纯等待,要熬到超时。两者的提示都是重试事务,原因和处理方式不同。

分步演示

锁等待超时
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.10.0 与 Redis 8.10.2(Homebrew,每个实验自己起实例,端口 9150 以上,只监听 127.0.0.1,不碰本机已有的实例)/ Python 3.15.0 + PyMySQL(按各篇注明用到哪些及版本);报错文本都是真实复现时原样捕获的;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用两个自己起的 MySQL 26.10.0 实例(一个带 --performance-schema=ON,一个多带 --innodb-rollback-on-timeout=ON),PyMySQL 的 unix socket 连接;表 d.t 5 行;默认隔离级别 REPEATABLE READ,autocommit 在各节里按需要关掉
    • 没有验证:能否在运行时用 SET GLOBAL 修改 innodb_rollback_on_timeout(本篇只试了启动选项);READ COMMITTED 隔离级别下无索引 UPDATE 的加锁范围;间隙锁的更多情形(插入意向锁、唯一索引冲突);innodb_lock_wait_timeout 的上限与下限;超时时间对元数据锁(lock_wait_timeout,报错文本不同)的影响;长事务对 undo 和 purge 的影响;死锁的受害者选择规则(这次被回滚的是后发语句的一方,别的情形没有测)
    京ICP备2026067502号-1京公网安备11010502063487号