大表分批更新
一条更新语句改几百万行会长时间持锁、生成巨大事务、把复制延迟推高,中途失败还要等回滚。稳妥做法是拆成小批:每次处理固定行数并独立提交,让锁持有时间与单批代价都可控。两种数据库的切片手段不同:MySQL 支持在单表更新上直接排序加限行;PostgreSQL 没有这个语法,需要用物理行位置或主键区间来做切片,并且每批都要重新定位。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| MySQL 手段 | 单表更新可带排序与限行,每批一个短事务 |
| PostgreSQL 手段 | 无排序限行语法,用行位置子查询或主键区间切片 |
| 排序必要性 | 限定行数时必须同时指定排序,否则取哪批行不确定 |
| 节奏控制 | 批与批之间留出间隔,给复制与清理留出余量 |
典型使用场景
- 给历史数据补一个字段值,几百万行需要分次处理
- 状态机迁移:把某个旧状态批量改成新状态
- 数据清洗:分批修正格式错误的字段值
- 同步补录:按条件把遗漏标记的同步数据补齐
- 清理任务:按时间条件分批处理过期数据
两库分批写法
两库分批写法
-- MySQL:单表更新可排序加限行,每批一个短事务
UPDATE orders
SET synced = 1
WHERE synced = 0
ORDER BY id -- 必须排序,否则取哪批不确定
LIMIT 1000;
-- PostgreSQL:没有排序限行,用行位置子查询切片
WITH batch AS (
SELECT ctid FROM orders
WHERE synced = false
ORDER BY id
LIMIT 1000
)
UPDATE orders o
SET synced = true
FROM batch b
WHERE o.ctid = b.ctid;
-- 更稳的替代:按主键区间推进,脚本记录进度便于续跑
-- UPDATE orders SET synced = true
-- WHERE synced = false AND id BETWEEN 1 AND 10000;切片方式对照
| 切片方式 | MySQL | PostgreSQL |
|---|---|---|
| 排序限行 | 单表更新支持 | 不支持 |
| 行位置切片 | 无对应概念 | 可用行位置子查询 |
| 主键区间切片 | 支持,最通用 | 支持,最通用 |
| 多表更新 | 不支持排序限行 | 用连接或子查询条件 |
| 断点续跑 | 脚本记录已处理区间 | 脚本记录已处理区间 |
行位置是物理位置,行被更新后会变化。并发写入频繁的场景用主键区间更稳,行位置切片则要求每批都重新计算,不能把位置缓存下来复用。
每批之间加短暂停顿可以让清理线程和被阻塞的写请求有喘息机会。批量大小的经验区间是几百到几千行,具体按单批耗时与锁等待情况调整。