应分批更新以避免锁表和日志膨胀;MySQL中全量UPDATE易升级为表锁,PostgreSQL则面临事务膨胀与WAL压力;实际需按主键分片、限流执行,如WHERE id > last_id AND id <= last_id + 1000。

为什么不能直接用 UPDATE ... WHERE 处理千万级表
大表全量更新会锁表、占满日志空间、拖垮数据库响应,尤其在 MySQL 的 InnoDB 引擎下,UPDATE 默认走行锁+间隙锁,一旦 WHERE 条件没走索引或范围过大,很容易升级成表锁。PostgreSQL 虽无锁表问题,但事务膨胀(bloat)和 WAL 写入压力同样致命。真实场景中,UPDATE t SET status = 1 WHERE create_time 这种语句跑一小时还卡住,不是因为慢,而是被其他查询堵死或触发了自动 kill。
<p>关键判断:只要影响行数预估超 10 万,就该放弃单条 <code>UPDATE。
分批更新必须带主键/唯一索引条件
靠时间字段或非索引字段分页(如 OFFSET)在大表上极不可靠:数据插入、删除会导致偏移错位,漏更或重复更新。唯一安全的方式是用主键或带唯一索引的字段做游标式推进。
OFFSET)在大表上极不可靠:数据插入、删除会导致偏移错位,漏更或重复更新。唯一安全的方式是用主键或带唯一索引的字段做游标式推进。
实操建议:
- 优先用自增
id或时间戳+唯一组合(如(create_time, id))作为分片依据 - 每次更新固定行数(建议 1k–5k),避免单次事务过大
- WHERE 条件必须包含
id > last_id且id ,不要用 <code>LIMIT OFFSET - 更新完一批后,立刻记录当前最大
id到临时表或外部存储,防止进程中断后无法续跑
UPDATE orders SET status = 2 WHERE id > 1000000 AND id <= 1005000 AND status = 0;
MySQL 和 PostgreSQL 分批写法差异
MySQL 不支持 UPDATE ... LIMIT 的子查询嵌套,但允许直接加 LIMIT;PostgreSQL 则不支持 LIMIT 在 UPDATE 中,必须用 CTE 或子查询模拟。
常见错误:
- 在 MySQL 中写
UPDATE t SET x=1 WHERE id IN (SELECT id FROM t WHERE ... LIMIT 1000)→ 报错You can't specify target table 't' for update in FROM clause - 在 PostgreSQL 中直接写
UPDATE t SET x=1 WHERE id IN (SELECT id FROM t LIMIT 1000)→ 实际可能更新远超 1000 行(因子查询未绑定外层条件)
正确做法:
- MySQL:
UPDATE orders SET status = 2 WHERE id BETWEEN ? AND ? AND status = 0,用参数传入区间 - PostgreSQL:
WITH batch AS (SELECT id FROM orders WHERE status = 0 AND id > ? ORDER BY id LIMIT 1000) UPDATE orders SET status = 2 FROM batch WHERE orders.id = batch.id
别忽略事务提交与监控节奏 批量更新不是“调大 batch 就快”,而是要平衡事务大小与系统负载。太小(如 100 行/批)导致网络和事务开销占比过高;太大(如 10 万/批)容易触发 long transaction、主从延迟飙升、甚至 OOM。
实操要点:
- 每批执行后强制
COMMIT,禁止长事务跨多批 - 加
SLEEP(0.1)(MySQL)或pg_sleep(0.05)(PG)控制吞吐节奏,给其他查询让出资源 - 监控
innodb_row_lock_waits(MySQL)或pg_stat_activity中长时间 running 状态 - 生产环境务必先在从库或影子表验证逻辑,尤其是 WHERE 条件是否真的只命中目标行
UPDATE 会触发二级索引回表、生成大量 undo log、或让复制线程积压。分批只是手段,核心是让每一批都可中断、可验证、可监控。


















