根本原因是事务持有锁时间过长,而非事务规模大;innodb行锁需至commit/rollback才释放,混入http调用、文件读写、未走索引的全表扫描或长批量更新等均会延长锁持有时间,导致后续操作阻塞超时。

为什么大事务容易触发 Lock wait timeout exceeded
根本原因不是“事务太大”,而是事务持有锁的时间太长。InnoDB 行锁不会在语句执行完就释放,而是要等到整个事务 COMMIT 或 ROLLBACK 才释放。只要事务没结束,它锁住的每一行(甚至间隙)都持续阻塞其他并发更新。
常见诱因包括:
- 事务内混入 HTTP 调用、文件读写、循环计算等非数据库操作
- SELECT FOR UPDATE 后做复杂业务逻辑,再 UPDATE
- 单次批量更新 10 万行且在一个事务里提交
- 隔离级别为 REPEATABLE READ 时,范围条件未走索引,触发大面积间隙锁
如何拆分批量更新避免锁堆积
不要用一个事务硬扛全量数据。把大更新切成小片,每片独立提交,让锁及时释放。
实操建议:
- 每批控制在 100~500 行之间:太少会增加网络和事务开销,太多仍可能超时
- 用主键或自增 ID 分片,避免 OFFSET 分页(深分页性能差且易漏行)
- 示例逻辑:
SELECT id FROM big_table WHERE status = 'pending' ORDER BY id LIMIT 500;
拿到这批
id 列表后,再执行:UPDATE big_table SET status = 'done' WHERE id IN (1,2,3,...);
- 每批更新后立刻
COMMIT,不要等下一批再提交哪些操作必须移出事务块
任何不涉及数据库写入的操作,都不该留在事务里——它们只会延长锁生命周期,却不贡献一致性保障。
明确要剥离的有:
- 外部 API 调用(如调支付网关、发短信、写 Kafka)
- 日志写入(特别是同步写磁盘的 log.info())
- 文件生成或上传(如导出 Excel 并存到 OSS)
- 循环处理结果集并拼装 JSON 字符串等纯内存操作
- 第三方 SDK 的耗时方法(如 PDF 渲染、OCR 识别)
替代方案:
- 改用消息队列异步执行(如 RabbitMQ / RocketMQ)
- 使用本地事件 + 最终一致性补偿(如更新状态后发事件,监听器重试)
- 在事务外查 ID 列表,事务内只做精准 UPDATE ... WHERE id IN ()
索引缺失会让行锁退化成表锁
这是最隐蔽也最高发的根因。InnoDB 只能在索引路径上加行锁;一旦 WHERE 条件无法命中索引,就会退化为全表扫描 + 意向排他锁(IX),效果等同于锁整张表。
验证方式:
- 对你的更新语句跑 EXPLAIN:
EXPLAIN UPDATE big_table SET status = 'done' WHERE biz_code = 'xxx';
- 如果
type 是 ALL 或 index,说明在扫全表- 如果
key 是 NULL,代表没走索引补救措施:
- 给高频更新字段建单列索引,如 ADD INDEX idx_biz_code (biz_code)
- 多条件组合更新,优先建联合索引,顺序按 WHERE 中等值条件在前、范围条件在后,例如:(status, created_at) 而非 (created_at, status)
- 注意隐式类型转换:user_id 是 BIGINT,但传字符串 '123' 就会让索引失效
真正卡住系统的,往往不是锁本身,而是事务里那些“看起来无关紧要”的几行日志或一次远程调用。锁等待超时错误报出来时,问题早已不在数据库层,而在代码组织方式里。











