单条UPDATE千万行会卡死数据库,因其触发全表扫描、长事务持锁、undo log与binlog爆炸性增长,并导致主从延迟飙升、CPU打满及大量接口超时。

为什么单条UPDATE千万行会卡死数据库
直接执行 UPDATE big_table SET status = 2 WHERE created_at 这类语句,在千万级表上不是“慢”,而是“危险”。它会触发全表扫描(若 created_at 无索引)、持有行锁+间隙锁直到事务结束、写满 undo log、产生巨量 binlog,同时主从延迟飙升。更糟的是,KILL 后回滚时间可能比执行还长。
分批更新必须满足的三个硬条件
拆事务不是随便加个 LIMIT 就完事,得守住三条底线:
- WHERE 条件字段必须有**有效索引**,否则每次
LIMIT都是全表扫,越往后越慢 - 每次更新的行数建议控制在
1000~5000,太大易耗尽 undo 空间,太小则网络/事务开销占比过高 - 两次提交之间必须加
SLEEP(至少0.1秒),给从库消化 binlog、释放锁、让其他业务请求插队的机会
用 WHERE + ORDER BY + LIMIT 实现可续传分批
靠 LIMIT 直接切片容易漏数据或重复——因为没指定顺序,InnoDB 扫描顺序不保证稳定。正确做法是锚定一个单调递增字段(如主键 id 或时间戳 updated_at):
id
- 每次循环前执行
START TRANSACTION - 更新语句后立刻查
ROW_COUNT(),为 0 则退出循环 - 只在确认有修改时才
COMMIT;出错就ROLLBACK并记录日志 - 务必在
COMMIT后再SLEEP,避免锁持续持有
最容易被忽略的是:不要在脚本里用 autocommit=0 一开到底,那等于把所有批次塞进一个超长事务里,跟没拆一样。











