为什么 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_waits\G,它会直接显示阻塞者和被阻塞者的
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 的锁生命周期——它什么时候开始持锁?持的是什么粒度的锁?其他事务哪条语句可能和它重叠?这些问题比“怎么查锁”更关键。
