调大innodb_lock_wait_timeout不能解决锁冲突本质问题,仅延长等待时间;根因在于长事务、未走索引的DML、事务内耗时操作等,需从sql设计、索引优化、事务边界入手。

为什么 innodb_lock_wait_timeout 调大不一定解决问题
这个参数只控制事务在等待行锁时最多忍多久,超时就报 Lock wait timeout exceeded。但它不减少锁冲突本身,只是让失败来得更快或更慢。真实瓶颈往往在事务设计:长事务、没走索引的 UPdate、在事务里做 rpc 或文件操作,都会把锁占着不动。
实操建议:
- 用
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60找出运行超 1 分钟的事务 - 检查这些事务里有没有
UPDATE或delete没命中索引(看EXPLAIN的type是否为ALL或index) - 避免在事务内调用外部服务、生成 PDF、发邮件等耗时操作
如何快速定位谁在锁谁
mysql 8.0+ 可直接查 performance_schema.data_locks 和 data_lock_waits,但更通用且直观的方式是组合查 INNODB_TRX、INNODB_LOCKS(5.7)、INNODB_LOCK_WAITS(已废弃,但兼容性好),或直接用 sys.innodb_lock_waits 视图。
实操建议:
- 执行
select * FROM sys.innodb_lock_waitsG,它会直接显示阻塞者和被阻塞者的trx_id、SQL、等待时间 - 对应到
SELECT * FROM INNODB_TRX WHERE trx_id = 'xxx'查具体事务语句和启动时间 - 注意
trx_state = 'LOCK WAIT'表示正在等锁,trx_state = 'RUNNING'且trx_started很早,很可能是“钉子户”
UPDATE/DELETE 没走索引导致全表扫描加锁
InnoDB 行锁是建立在索引上的;如果 WHERE 条件没命中索引,就会退化成表级意向锁 + 大量记录的间隙锁或记录锁,极大增加冲突概率。常见于 UPDATE user SET status=1 WHERE phone='138...' AND deleted=0,但 phone 没建索引。
实操建议:
- 对所有
UPDATE/DELETE的WHERE字段,确认是否都有单列索引或复合索引前导列匹配 - 用
EXPLAIN format=tree(8.0)或EXPLAIN看key列是否为NULL,rows是否远大于实际匹配数 - 特别警惕
OR、函数包装字段(如WHERE DATE(create_time) = '2024-01-01')、隐式类型转换(varchar字段传数字)
事务隔离级别对锁等待的影响
默认 REPEATABLE READ 下,普通 SELECT 不加锁,但 UPDATE/DELETE 会加记录锁 + 间隙锁(防止幻读),锁范围更大、持有时间更长;而 READ COMMITTED 只加记录锁,间隙锁只在唯一索引等少数场景生效,锁更轻。
实操建议:
- 业务能接受“不可重复读”的,可考虑将库或会话设为
SET session TRANSACTION ISOLATION LEVEL READ COMMITTED - 注意
READ COMMITTED下UPDATE ... WHERE仍可能锁住非匹配行(如唯一索引查找失败时的插入意向锁),不能完全消除等待 - 避免在高并发写场景下使用
SERIALIZABLE,它会让普通SELECT也加共享锁,极易雪崩
锁等待优化最常被忽略的一点:不是调参或加索引就能一劳永逸,而是要从应用层明确每条 SQL 的锁生命周期——它什么时候开始持锁?持的是什么粒度的锁?其他事务哪条语句可能和它重叠?这些问题比“怎么查锁”更关键。