<p>应优先排查长事务与行级锁而非调参;通过SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started < NOW() - INTERVAL 1 MINUTE定位持锁事务,再联查INNODB_LOCK_WAITS确认阻塞关系,KILL blocking_trx_id对应线程。</p>

直接调大 innodb_lock_wait_timeout 是最危险的“解法”——它不解决锁争抢,只延迟报错,容易把连接池拖垮、掩盖真实瓶颈。
查清谁在真正持锁不放
报错的事务只是“等死”的那个,真正该 KILL 的是它前面那个迟迟不提交的事务。用这两条 SQL 定位阻塞源头:
SELECT t1.TRX_ID AS waiting_trx_id, t1.TRX_QUERY AS waiting_query,
t2.TRX_ID AS blocking_trx_id, t2.TRX_QUERY AS blocking_query,
t2.TRX_STARTED AS blocking_started
FROM INFORMATION_SCHEMA.INNODB_TRX t1
INNER JOIN INFORMATION_SCHEMA.INNODB_LOCK_WAITS w ON t1.TRX_ID = w.BLOCKING_TRX_ID
INNER JOIN INFORMATION_SCHEMA.INNODB_TRX t2 ON w.BLOCKING_TRX_ID = t2.TRX_ID;
- 结果为空?说明锁已释放,或已被 InnoDB 自动回滚为死锁(查 error log 看
Deadlock found) -
blocking_started时间早于 60 秒?基本可断定是应用未提交或逻辑卡住 -
KILL的目标是blocking_trx_id对应的线程 ID,不是报错事务自己的 ID - 确认
t2.TRX_STATE是RUNNING,否则它也在等别人,不是根因
批量 UPDATE/DELETE 必须避免全表扫描
没有索引的 WHERE 条件会让 InnoDB 升级为表锁或大量行锁,批量操作极易触发超时。比如这个语句:
UPDATE device_data_message SET device_id = 45 WHERE monitor_id IN ( SELECT monitor_id FROM device_data_message WHERE pillar_id = 10010 );
- 执行计划里出现
type: ALL?立刻补索引:ALTER TABLE device_data_message ADD INDEX idx_pillar_collect (pillar_id, collect_type) - 子查询嵌套太深?改用 JOIN 或先查出 ID 列表再分批 UPDATE
- WHERE 条件字段没被索引覆盖?复合索引顺序必须匹配查询条件顺序
事务不能包太大,尤其不能混日志和核心更新
一个事务里既更新用户状态、又写操作日志、再调外部接口,等于把锁持有时间拉长到网络+IO+业务逻辑总和。常见反模式:
- 事务内做 HTTP 调用、文件读写、复杂计算
- 循环中逐条
UPDATE而非拼成IN (id1,id2,...)一次执行 - 日志记录(如审计表插入)放在主事务里,而非用异步队列或
AFTER COMMIT钩子 - SELECT + UPDATE 没加
FOR UPDATE却依赖一致性?那要重评估隔离级别是否真需要 RR
调参前先对齐应用层超时配置
innodb_lock_wait_timeout 不是独立参数,它必须和客户端行为协同:
- Spring Boot 的
spring.datasource.hikari.connection-timeout或 MyBatis 的queryTimeout必须 ≥ MySQL 设置值,否则客户端先断连,MySQL 还在等 - 设成 0?官方明确禁止——无限等待会迅速耗尽连接数
- 全局改配置(
/etc/my.cnf中innodb_lock_wait_timeout = 30)需重启 MySQL;会话级SET innodb_lock_wait_timeout = 30只对当前连接生效 - 有重试机制的应用,30~60 是安全范围;无重试,调大反而让用户等更久
锁超时从来不是孤立错误,它是某条慢 SQL、某个未提交事务、或一处缺失索引暴露出来的压力测试结果。盯紧 INFORMATION_SCHEMA.INNODB_TRX 和 EXPLAIN 输出,比调参重要十倍。


















