← 中文资源

简体中文版本

分批安全地更新超大表

按键分块、逐批提交、记录进度并支持断点续跑,避免单个巨型事务地重写数百万行。

阅读英文原文

目标

一条更新四千万行的 UPDATE 写起来简单、跑起来糟糕:锁保持一小时、WAL 尖峰拖垮复制、跑到 95% 失败还得全部回滚。同样的工作拆成五万行一批,每批亚秒级提交,凭 WHERE 谓词天然可断点续跑,并能按复制承受力自我限速。无聊、可观察、可重启,胜过又快又灾难。

步骤要点

  1. 先统计待处理行数,按 5 万一批算出约 800 次提交,确认单批锁时间在亚秒级。
  2. 单批事务用“子查询按 id 排序取 LIMIT 50000 FOR UPDATE SKIP LOCKED”圈定范围,允许并行 worker 互不碰撞地领任务。
  3. 外层 while 循环反复执行单批,更新数归零即退出,批间 sleep 1 给复制留喘息。
  4. 循环期间盯 pg_wal_lsn_diff 复制延迟,一旦爬升就拉长间隔而不是缩小批次。
  5. 收尾统计剩余 NULL 行数为零后,ALTER COLUMN region SET NOT NULL 因已满足约束而瞬时完成。

排错提示

越跑越慢是因为每轮都在全表扫 region IS NULL,应改为按 id 的 keyset 范围分块走索引切片;复制延迟持续爬升就插入与延迟成比例的休眠甚至暂停循环,持续滞后会饿死故障切换与备份;某批中途失败时事务已回滚、不存在半批状态,直接重跑循环即可从仍为 NULL 的行继续。

适用边界

分块用总时长换安全,整体跑得比单条语句慢;全部完成前读端会同时看到新旧值,下游任务要容忍中间态。

代码示例

UPDATE orders
SET region = compute_region(country)
WHERE id IN (
  SELECT id FROM orders
  WHERE region IS NULL
  ORDER BY id
  LIMIT 50000
  FOR UPDATE SKIP LOCKED
);