查清锁等待链应立即查询innodb_trx(找lock wait事务)、innodb_lock_waits(确认阻塞关系)和innodb_locks(定位具体行与索引),三表联动闭环定位,而非依赖易被覆盖的show engine innodb status。

查清谁在堵路:用系统视图抓实时锁等待链
错误刚报出来时,SHOW ENGINE INNODB STATUS\G确实能看,但它只保留最后一次死锁或锁等待快照,且容易被新事务覆盖。真正可靠的做法是立刻查三张系统表:
-
INFORMATION_SCHEMA.INNODB_TRX:找trx_state = 'LOCK WAIT'的事务,记下trx_mysql_thread_id和trx_started时间 -
INFORMATION_SCHEMA.INNODB_LOCK_WAITS:直接关联出blocking_trx_id和requested_trx_id,确认谁卡住了谁 -
INFORMATION_SCHEMA.INNODB_LOCKS:结合lock_trx_id和lock_rec_id,定位到具体哪一行、哪个索引页被锁住
别依赖SHOW PROCESSLIST——它只显示连接状态,不反映行级锁关系;也别等错误日志里出现 SQL 再查,那时锁可能早已释放。
为什么UPDATE会锁全表:索引缺失导致行锁退化
大表更新慢、锁超时,90%以上根因是没走索引。InnoDB 的行锁依赖索引,一旦WHERE条件无法命中索引(比如用name字段更新但没建索引),引擎就会退化为全表扫描+表级锁,所有并发更新都排队等一个锁。
验证方法很简单:EXPLAIN SELECT * FROM big_table WHERE name = 'xxx'; 如果type列是ALL或index,就说明在扫全表。
- 补索引不是万能解药,但能切掉80%锁问题:对高频更新字段加
INDEX,组合条件优先建联合索引 - 注意索引顺序:
WHERE status = ? AND created_at > ?应建(status, created_at)而非反过来 - 避免隐式类型转换:比如
user_id是BIGINT,但传入字符串'123',会导致索引失效
批量更新别在一个事务里硬扛:拆小、分批、移出非DB操作
锁等待时间 = 事务开始到提交之间所有操作耗时。哪怕只更新1000行,如果事务里混了HTTP调用、日志写入、循环计算,锁就一直挂着。
- 把
SELECT FOR UPDATE后长时间处理再更新,改成先查ID列表、再算逻辑、最后UPDATE ... WHERE id IN (...)单条提交 - 批量更新必须分片:每100~500行一个事务,用
COMMIT释放锁,而不是一次性提交10万行 - 发消息、调第三方API、生成文件这些操作,一律移出事务块,改用异步队列或最终一致性补偿
- 高并发场景慎用
REPEATABLE READ:改用READ COMMITTED可减少间隙锁范围,降低锁冲突概率
innodb_lock_wait_timeout不是救命稻草:设太长反而更糟
默认50秒看似宽松,但在接口层用户等50秒才失败,体验比立刻失败还差。这个参数本质是“兜底熔断”,不是性能优化手段。
- 线上核心接口建议设为
5或10秒:让失败更快暴露,前端可降级或重试 - 后台批处理作业可设为
120,但必须确保事务本身不超时(比如避免在事务里调外部HTTP) -
SET GLOBAL innodb_lock_wait_timeout = 10只对新连接生效,已连的连接不会变——这点极易踩坑 - 永久生效要改配置文件,加
innodb_lock_wait_timeout = 10再重启MySQL,但生产环境未必允许重启
真正难的是判断哪些操作该留在事务内、哪些该剥离,以及如何平衡一致性与响应速度——这没有银弹,得靠每次锁超时后回溯SQL执行路径和业务逻辑边界。











