大批量UPDATE会锁死数据库、日志爆炸、内存溢出,必须分批处理:按有索引的单调字段(如id)切片,每批固定范围更新并显式COMMIT,避免LIMIT无序风险;WHERE条件须走索引,优先用集合式写法(如INSERT…ON DUPLICATE KEY UPDATE)替代逐条更新。

为什么大批量 UPDATE 会直接拖垮数据库
不是“慢”,是锁死+日志爆炸+内存溢出三连击。InnoDB 在单事务里更新百万行,会持续持有行锁、疯狂写 redo/undo、撑爆 buffer pool,同时 binlog(ROW 格式)生成速度远超磁盘吞吐,主从延迟飙升到小时级——这时你看到的“崩溃”,其实是连接拒绝、CPU 打满、OS OOM killer 杀进程。
必须分批,且边界要可确定
LIMIT 单独用在 UPDATE 里极其危险:没 ORDER BY 时 MySQL 不保证顺序,同一批可能漏行或重复更新。正确做法是按有索引的单调字段切片:
- 先查范围:
SELECT MIN(id), MAX(id) FROM t WHERE status = 0 - 每次更新固定区间:
UPDATE t SET status = 1 WHERE id BETWEEN 100001 AND 105000 AND status = 0 - 每批后显式
COMMIT,并SLEEP 0.1缓解压力 - 应用层控制循环,别用存储过程里的
WHILE(易超时、难中断)
WHERE 条件不走索引,分批也白搭
执行前必须跑 EXPLAIN FORMAT=TREE,确认 type 不是 ALL。常见踩坑点:
-
WHERE DATE(created_at) = '2024-01-01'→ 改成created_at >= '2024-01-01' AND created_at - 多条件组合时,联合索引顺序错位,比如
WHERE status = 0 AND updated_at > '2024-01-01',索引必须是(status, updated_at),反过来就失效 - 字段上有触发器、外键或生成列,会额外开销;临时禁用需评估业务一致性,不能无脑关
替代方案:用 INSERT … ON DUPLICATE KEY UPDATE 或 MERGE
当你的“更新”本质是用新数据覆盖旧值(比如从临时表同步),集合式写法快得多,但前提很硬:
- MySQL:
INSERT INTO t (id, col) SELECT id, new_val FROM temp ON DUPLICATE KEY UPDATE col = VALUES(col) - PostgreSQL:
MERGE INTO t USING temp ON t.id = temp.id WHEN MATCHED THEN UPDATE SET col = temp.col - 要求主表
id是主键或唯一键,且temp表已建好索引;否则性能反不如分批UPDATE
真正容易被忽略的是:分批的“批大小”没有标准值——它取决于你的硬件(IOPS、内存)、并发负载和事务日志空间,5000 行在一台 8C16G 的机器上可能刚好,在另一台可能仍卡住。上线前务必在影子库压测,而不是靠经验拍脑袋。

















