应先定位锁等待源头而非调参:通过INFORMATION_SCHEMA.INNODB_TRX查LOCK WAIT事务,INNODB_LOCK_WAITS查阻塞关系,INNODB_LOCKS查具体行锁,再结合线程ID定位肇事SQL。

查清谁在堵路:快速定位锁等待源头
出现 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction,第一反应不该是调参数,而是看“谁卡住了谁”。InnoDB 的锁等待现场稍纵即逝,必须用系统视图抓实时快照:
-
SELECT * FROM information_schema.INNODB_TRX—— 查当前所有活跃事务,重点关注trx_state = 'LOCK WAIT'和trx_started时间过长的 -
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS—— 直接给出等待关系:blocking_trx_id是肇事者,requested_lock_id是受害者 -
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS—— 查具体锁在哪行(lock_trx_id、lock_rec_id),配合INNODB_TRX中的trx_mysql_thread_id就能定位到线程
别依赖 SHOW PROCESSLIST,它只显示连接状态,不反映行级锁关系;也别等错误日志里出现 SQL 再查——那时锁可能早已释放。
临时止血:调整 innodb_lock_wait_timeout 参数
这个参数不是“治本”,但能避免业务雪崩。默认 50 秒太长,高并发接口常等几十秒才报错,用户体验极差;设太短(如 1 秒)又容易误杀正常慢事务。建议按场景分级设置:
- 线上核心接口:设为
5或10秒,让失败更快暴露,前端可降级或重试 - 后台批处理作业:可设为
120,但必须确保事务本身不超时(比如避免在事务里调外部 HTTP) - 修改方式分两种:
SET innodb_lock_wait_timeout = 10只对当前会话生效;SET GLOBAL innodb_lock_wait_timeout = 10对新连接生效,**当前已连的连接不会变**,这点极易踩坑 - 永久生效要改配置文件(如
/etc/my.cnf),加一行innodb_lock_wait_timeout = 10,然后重启 MySQL —— 但生产环境未必允许重启
根因排查:为什么锁等这么久?
调参只是缓冲,真正要盯的是锁为啥不释放。常见硬伤有三个:
-
没走索引的 UPDATE/DELETE:比如
UPDATE users SET status=1 WHERE phone='138...',而phone没索引 → 全表扫描 → 锁住成千上万行 → 别的事务一碰就等。用EXPLAIN看执行计划,确认type是ref或range,不是ALL -
长事务没提交:事务里混了 RPC 调用、文件读写、sleep(1)……导致锁持有时长远超预期。查
INNODB_TRX表里的trx_started和trx_state,凡超过 5 秒未提交的都该预警 -
批量插入撞上自增锁:MySQL 5.6/5.7 中
innodb_autoinc_lock_mode = 1(默认)时,INSERT INTO t VALUES (),(),...会锁住整个自增计数器,直到语句结束。改成2可缓解,但需重启;更稳妥的是拆成小批量或用显式主键
代码与事务设计避坑要点
很多锁等待问题,根源不在数据库,而在应用层怎么用它:
- Spring @Transactional 默认传播行为是
REQUIRED,如果一个方法里先查再更新,且没加readOnly=true,那整个方法都在事务里——哪怕查操作根本不需要锁 - 避免“先 SELECT FOR UPDATE 再 UPDATE”的惯性写法,除非真需要校验中间状态;多数情况直接
UPDATE ... WHERE ...更轻量 - 多表更新务必固定顺序:比如总是先更新
orders再更新order_items,否则 A 事务按此顺序、B 事务反着来,死锁就来了 - ORM 自动生成 SQL 时,where 条件顺序可能不一致(如 MyBatis 动态 SQL 字段顺序随机),也会引发隐式锁序混乱,建议关键更新用固定字段顺序的 XML 显式写
锁等待超时从来不是孤立错误,它是并发逻辑、SQL 质量、事务边界三者共同暴露的切口。盯着 INNODB_TRX 和慢查询日志交叉比对,比盲目调参管用十倍。










