innodb_lock_wait_timeout默认非50秒,可被全局或会话动态修改;需用show global/session variables查值,警惕连接池初始化sql篡改;报错“lock wait timeout exceeded”不等于死锁,须结合innodb_trx、innodb_lock_waits、processlist定位阻塞源;select ... for update在rr级下也加临键锁,无索引易锁表;调参仅延缓问题,根治需缩短持锁时间、避免长事务、对齐spring事务timeout与mysql参数。

查 innodb_lock_wait_timeout 当前值和会话级别是否被改过
这个超时不是固定 50 秒,它可被全局或会话级动态修改,很多线上问题其实是某段代码里悄悄执行了 SET innodb_lock_wait_timeout = 5,导致后续事务极容易报错。别急着查死锁日志,先确认值本身是否异常。
- 查全局值:
SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout'; - 查当前会话值:
SHOW SESSION VARIABLES LIKE 'innodb_lock_wait_timeout'; - 注意:应用连接池(如 HikariCP)常在初始化连接时执行
SET,得去连接池配置或初始化 SQL 里翻
错误信息里看到 Lock wait timeout exceeded; try restarting transaction 就一定是锁冲突?
不一定。这个报错只说明“等锁等超时了”,但不透露谁在持锁、持的什么锁、甚至不保证对方事务还活着。MySQL 不会主动告诉你阻塞源,得靠关联视图手动挖。
- 立刻查
INFORMATION_SCHEMA.INNODB_TRX,看TRX_STATE = 'LOCK WAIT'的事务及其TRX_QUERY - 再联查
INFORMATION_SCHEMA.INNODB_LOCK_WAITS找BLOCKING_TRX_ID - 最后用该 ID 去
INNODB_TRX查持锁事务的TRX_MYSQL_THREAD_ID,再查PROCESSLIST看它在跑什么 - 常见坑:持锁事务可能已断开但未提交(比如客户端崩溃),这时
PROCESSLIST里看不到,但锁还在 —— 必须人工KILL对应线程 ID 或等超时回滚
为什么 SELECT ... FOR UPDATE 也会触发锁等待超时?
很多人以为只有 UPDATE/DELETE 才加锁,其实 SELECT ... FOR UPDATE 在可重复读(RR)隔离级别下会加临键锁(next-key lock),如果 where 条件没走索引,就会锁全表或大范围索引区间,极易被其他事务阻塞。
- 检查执行计划:
EXPLAIN SELECT ... FOR UPDATE,确认type是range或ref,而非ALL或index - 避免在无索引字段上做
FOR UPDATE,例如SELECT * FROM t WHERE status = 'pending' FOR UPDATE(status无索引) - RR 级别下,即使
WHERE id = ?走主键,若id不存在,InnoDB 仍会在间隙加锁 —— 这个间隙锁也可能被别的插入阻塞
调整 innodb_lock_wait_timeout 是治本还是掩耳盗铃?
调大它(比如设成 120)只是让报错来得晚一点,不能减少锁冲突;调小(比如 5)会让问题暴露更早,但也可能把本可成功的小争用直接干掉,增加业务重试压力。
- 真正要做的:定位并缩短持锁时间 —— 比如把事务里非 DB 操作(HTTP 调用、计算)挪到事务外
- 检查是否有长事务没提交:查
TRX_STARTED时间戳,超过几分钟的就得预警 - 注意:该参数对
LOCK TABLES无效,只影响 InnoDB 行锁等待 - 最易被忽略的一点:Spring @Transactional 的
timeout属性和这个 MySQL 参数是两套机制,前者控制事务管理器层面的超时,后者控制存储引擎层,必须两边对齐,否则会出现“Java 层已回滚但 MySQL 锁还在”的诡异状态










