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

为什么单条UPDATE千万行会卡死数据库
直接执行 UPDATE big_table SET status = 2 WHERE created_at 这类语句,在千万级表上不是“慢”,而是“危险”。它会触发全表扫描(若 <code>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):
UPDATE big_table SET status = 2 WHERE id > 1000000 AND id <= 1005000 AND created_at < '2025-01-01';
然后循环推进 id 范围。这样即使中途失败,也能从最后成功批次的 id 继续,不会丢或重。别用 OFFSET,它在大偏移时性能断崖式下跌。
Python脚本里怎么安全控制事务边界
关键不是“自动提交关不关”,而是显式管理每个批次的生命周期:
- 每次循环前执行
START TRANSACTION - 更新语句后立刻查
ROW_COUNT(),为 0 则退出循环 - 只在确认有修改时才
COMMIT;出错就ROLLBACK并记录日志 - 务必在
COMMIT后再SLEEP,避免锁持续持有
最容易被忽略的是:不要在脚本里用 autocommit=0 一开到底,那等于把所有批次塞进一个超长事务里,跟没拆一样。


















