← 中文资源

简体中文版本

用 COPY 取代逐行插入以提速批量导入

把数千次单行 INSERT 往返替换为一条 COPY 流,量化差异,并保持导入可续跑、可幂等。

阅读英文原文

目标

一次一条地插九十万行,等于把网络往返、解析与计划各付九十万次。COPY 协议把数据当作一根连续管道流过去,零逐行开销,提速通常一到两个数量级。让它保持安全的模式不变:先落草稿表,再幂等 upsert,于是重跑与坏行都变成常规操作而非灾难。

步骤要点

  1. 先量化逐行插入基线:900 万次往返、40 分钟,这是重写必须打败的数字。
  2. 用 pg-copy-streams 把 CSV 经 pipeline 流入 COPY staging_events FROM STDIN (FORMAT csv)。
  3. 从 staging 用 ON CONFLICT (id) DO UPDATE 幂等并入目标表,崩溃重跑不丢已提交数据、不产生重复。
  4. 端到端计时:同样九十万行约一分钟,约 40 倍吞吐提升。
  5. 加载期间监控复制延迟;单个巨型 COPY 的 WAL 突发若影响副本,就把文件切成多个 COPY 事务分块。

排错提示

COPY 遇到坏行整批失败,应先载入约束宽松的 staging,清洗隔离坏行后再 upsert 幸存者;驱动不支持 COPY 就退回 psql \copy 文件方式,同样走 COPY 协议;重跑出现重复说明直接 COPY 进了目标表——务必先进 staging。另外 COPY 绕过应用层的逐行校验钩子,这部分责任由 staging 清洗承接。

适用边界

单个 COPY 即单个事务,超大导入仍需为复制与锁健康分块;staging 模式占用额外临时磁盘。

代码示例

const stream = client.query(
  copyFrom("COPY staging_events (id, kind, payload, occurred_at) FROM STDIN WITH (FORMAT csv, HEADER true)")
);
await pipeline(fs.createReadStream("events.csv"), stream);

// Then upsert idempotently
INSERT INTO events ... SELECT ... FROM staging_events
ON CONFLICT (id) DO UPDATE SET payload = EXCLUDED.payload;