最急的是定位“谁卡住了锁”,information_schema.innodb_trx是第一手线索:执行select * from information_schema.innodb_trx where trx_state = 'running' and trx_started。

查谁在锁着不放
报错时最急的不是调参数,而是立刻定位“谁卡住了锁”。information_schema.INNODB_TRX 是第一手线索:
- 执行
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started 找出运行超1分钟的事务——它们大概率没提交也没回滚 - 重点看
trx_mysql_thread_id和trx_query:前者是线程ID,后者是它正在跑的SQL;如果trx_query是空,说明事务已开启但还没执行语句(比如应用层开了事务但卡在业务逻辑里) - 配合
SHOW PROCESSLIST;查对应ID的状态:若为Sleep且Time很大,基本就是“开完事务就挂了”
别只盯着等待方——持有锁的事务往往安静得像不存在,但它才是根因。
批量UPDATE没索引?直接锁全表
这是生产环境最常被忽略的坑:一个没走索引的 UPDATE 或 DELETE,InnoDB 会放弃行锁,升级为表级锁或间隙锁扫描。你看到的“锁等待”,其实是整个表被堵死。
- 用
EXPLAIN检查你的批量语句:如果type是ALL或index,且rows高得离谱,说明在全表扫 -
WHERE条件字段必须有索引,且索引能被实际命中(比如避免在索引列上用函数:WHERE DATE(create_time) = '2026-08-12'就无法走索引) - 对大表做批量更新,宁可拆成每次 1000 行的循环,也别一发干到底——
UPDATE t SET x=1 WHERE id BETWEEN ? AND ?比UPDATE t SET x=1 WHERE status=0(无索引)安全得多
索引不是锦上添花,是锁粒度的生死线。
别盲目调大 innodb_lock_wait_timeout
设成 120 秒甚至 300 秒,只是把报错延迟了,反而让问题更难发现。真正危险的是“慢事务+长等待”形成的雪球效应。
-
SET GLOBAL innodb_lock_wait_timeout = 120;只应在临时救火时用,且必须同步监控:调大后,你要盯紧INNODB_TRX里trx_wait_started时间持续增长的事务 - 事务内不要混杂耗时操作(如远程HTTP调用、复杂计算),这些会让锁持有时间不可控
- 应用层写事务时,明确标注超时:Spring 的
@Transactional(timeout = 30)比数据库层的 50 秒更早兜底
锁超时不是配置项,是系统健康度的报警阈值——压低它,才能逼出真问题。
死锁不是等出来的,是顺序乱出来的
两个事务互相等对方释放资源,本质是访问资源的顺序不一致。MySQL 能检测死锁并杀掉其中一个,但 Lock wait timeout exceeded 更多是单向阻塞,背后往往是隐性顺序依赖。
- 所有事务按相同顺序访问表和主键范围:比如先更新
order表再更新order_item,就别在某处反着来 - 避免在事务里分批查询再更新:
SELECT id FROM t WHERE ...; UPDATE t SET ... WHERE id IN (...);这种模式极易因并发导致不同事务拿到不同id集合,进而按不同顺序加锁 - 用
SELECT ... FOR UPDATE时,务必确保 WHERE 条件唯一且走索引——否则可能锁住不该锁的间隙
顺序一致性比锁本身更难调试,它藏在代码路径里,不在SQL里。











