MySQL Lock wait timeout exceeded(1205)怎么查:8.0.46 阻塞链实测,超时 5 秒整、语句回滚事务还在
在 MySQL 8.0.46 测试库复现 Lock wait timeout exceeded(错误 1205):innodb_lock_wait_timeout=5 时等待方精确 5.002 秒报错;用 performance_schema.data_locks / data_lock_waits、sys.innodb_lock_waits、SHOW ENGINE INNODB STATUS 找到根源会话;证明 1205 只回滚当前语句、事务仍开着(INSERT 还在);并和死锁 1213 对比。附复现脚本、只读巡检 SQL、样例 CSV。
MySQL 8.0.46 实测(测试库、自造 accounts 1 万行):会话 A 持有 id=1 的行锁不提交,会话 B 去改同一行。把 innodb_lock_wait_timeout 调到 5,B 从发出 UPDATE 到报 ERROR 1205 正好 5.002 秒;调到 2 是 2.001 秒。默认 50 秒时,只要 A 在超时前提交,B 就能成功(本次 A 持锁 3 秒,B 约 2 秒后提交,余额 1095)。1205 和死锁 1213 不一样:它只回滚当前那条语句,事务还开着——我在同一事务里先 INSERT 再撞 1205,INSERT 的行还在,trx_rows_modified=1。
适用对象
- 1205 的等待上限就是 innodb_lock_wait_timeout(默认 50 秒)。本次调到 5 和 2,等待方分别精确等了 5.002 秒和 2.001 秒后报错。
- 排查顺序:performance_schema.data_lock_waits 看谁等谁 → data_locks 看锁模式和 LOCK_DATA → sys.innodb_lock_waits 看两边 SQL → innodb_trx 看事务年龄和改动行数。本次抓到 WAITING / GRANTED 都是 X,REC_NOT_GAP,LOCK_DATA=1。
- 1205 只回滚当前语句,不回滚事务。同一事务里先 INSERT 再 UPDATE 撞超时:INSERT 仍在,information_schema.innodb_trx 里 trx_state=RUNNING、trx_rows_modified=1;ROLLBACK 后 INSERT 才消失。默认 mysql 客户端遇错即停,要用 --force 才能看到这一点。
- 和 1213 死锁的区别:死锁是环,InnoDB 立刻回滚整个事务(SQLSTATE 40001);1205 是单边超时,只丢当前语句。应用侧重试策略不能混用。
1205 到底回滚了什么
innodb_lock_wait_timeout 是「一个事务在等行锁时最多等多久」。默认 50 秒。到点后,InnoDB 放弃当前这条语句,返回 1205;事务本身不结束,前面已经成功的语句还在未提交状态。
所以应用侧看到 1205 以后,如果直接重试「最后一条 UPDATE」而不先 ROLLBACK,可能在一个半残的事务里继续写,锁还占着、数据也不一致。正确做法通常是:捕获 1205 → ROLLBACK → 按业务决定是否从 BEGIN 重试整段。
超时真的按参数走:5.002 秒和 2.001 秒
会话 A:BEGIN; UPDATE accounts SET balance=balance-10 WHERE id=1; 然后 SLEEP 很久再 COMMIT。会话 B 晚 1 秒:BEGIN; UPDATE … balance=balance+5 WHERE id=1。
B 的会话级 timeout=5 时,从发出 UPDATE(b_start=10:23:36.284)到报 1205(b_end=10:23:41.286)正好 5.002 秒。timeout=2 时是 2.001 秒。A 提交后余额是 1090——只有 A 的 −10 生效,B 的 +5 没写上。
对照:timeout 保持默认 50,但 A 只持锁 3 秒就提交——B 等了大约 2 秒后成功,最终余额 1095(1100−10+5)。说明「调小 timeout」不会让业务更快成功,只是让失败来得更早;根因还是长事务占着锁。
| 场景 | timeout | A 持锁 | B 等待 | B 结果 | 最终 balance |
|---|---|---|---|---|---|
| T5 | 5 s | 12 s | 5.002 s | ERROR 1205 | 1090 |
| T2 | 2 s | 8 s | 2.001 s | ERROR 1205 | 1090 |
| T50ok | 50 s(默认) | 3 s | ≈2 s | 提交成功 | 1095 |
用 data_lock_waits 把阻塞链抓出来
B 还在等的那几秒里,另开一个会话跑巡检。performance_schema.data_lock_waits 给出 waiting_trx / blocking_trx;data_locks 里同一行:一方 GRANTED X,REC_NOT_GAP,一方 WAITING X,REC_NOT_GAP,LOCK_DATA=1(就是主键 id=1)。
sys.innodb_lock_waits 更直白:waiting_query 是那条 UPDATE …+5,blocking_query 是 SELECT SLEEP(12)——A 已经改完行、正睡在事务里。information_schema.innodb_trx 里等待方 trx_state=LOCK WAIT,trx_rows_locked=1,trx_rows_modified=0(还没改成)。
三层链也能看见:A 持锁,B 等 A,C 等同一行——data_lock_waits 同时出现 B←A、C←A、C←B。根因会话是 A(RUNNING、age 最大、持有 GRANTED),不是链尾的 C。处理顺序:找业务负责人确认 A 在干什么 → 能提交就催提交 → 确认可以再 KILL;不要先杀等待方。
本节判断
- SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 段也能看到锁等待,但只保留当前快照;高并发时优先用 performance_schema,可脚本化、可 JOIN。
- 只读巡检 SQL(下载区)7 段:超时参数、data_lock_waits、data_locks、sys 视图、innodb_trx、≥30 秒长事务、processlist。生产请自行脱敏 query 文本。
1205 之后事务还在:INSERT 没丢
这是我自己踩过的坑:以为 1205 等于整段失败,结果连接池把同一个未提交事务借给下一个请求。复现:B 在同一事务里先 INSERT orders(amount=999),再去 UPDATE 被 A 锁住的行。
客户端必须加 --force,否则默认遇错退出,后面的 SELECT 根本不跑。加了之后:报 1205 之后立刻查,amount=999 的行还在(cnt=1);innodb_trx 里这条连接 trx_state=RUNNING,trx_rows_modified=1。ROLLBACK 之后 cnt 才变 0。
结论写进应用规范:捕获 1205 / 1213 / 锁相关错误 → 显式 ROLLBACK(或关掉连接让服务端回滚)→ 再重试。不要假设「报错了事务一定没了」。
1205 和死锁 1213 不要用同一套重试
死锁 1213:InnoDB 检测到锁环,立刻回滚其中一个事务(整个事务),SQLSTATE 40001。站内另一篇用相反顺序转账复现过——受害方约 1.3 秒就报 1213,没有等满 timeout。
1205:没有环,只是单边等锁到点。回滚范围是当前语句。如果业务逻辑是「多步必须原子」,应用必须自己 ROLLBACK 再整段重试;如果只是单条写、且前面没写过,重试该语句可以,但仍建议统一走「先滚再试」。
把 timeout 从 50 调到 5,可以让失败来得更快、连接更快释放,但也会让「本来再等 2 秒就能成功」的请求失败(见上面 T50ok)。先治长事务和锁顺序,再谈参数。
觉得有用?转给正在做这件事的同事或朋友
参考依据
以下来源用于确认市场趋势、政策背景和术语边界;具体落地方案仍以客户的数据范围、权限和交付目标为准。
常见问题
MySQL Lock wait timeout exceeded 是什么意思?
一个会话在等行锁时等满了 innodb_lock_wait_timeout(默认 50 秒),InnoDB 放弃当前语句并返回错误 1205。本次把超时调到 5 秒,等待方精确 5.002 秒后报错。
1205 会回滚整个事务吗?
不会,只回滚当前语句。本次同一事务里先 INSERT 再撞 1205,INSERT 仍在,trx_rows_modified=1;ROLLBACK 后才清除。
怎么找到是谁堵住了我?
查 performance_schema.data_lock_waits 和 data_locks 看 waiting/blocking 事务与 LOCK_DATA;sys.innodb_lock_waits 看两边 SQL;innodb_trx 看谁开得最久。根因一般是持有 GRANTED 且年龄最大的那个会话。
1205 和死锁 1213 有什么区别?
1213 是锁环,InnoDB 立刻回滚整个事务;1205 是单边超时,只回滚当前语句。重试策略不能混用。
把 innodb_lock_wait_timeout 调小能解决问题吗?
只能让失败更快出现。本次 timeout=50、持锁方 3 秒就提交时,等待方成功了;调小超时反而会把这类请求打成 1205。先处理长事务。