COPY FROM 比 INSERT 快因绕过 SQL 解析、触发器和参数绑定,直接写入存储层;但实际速度受 WAL 写入阻塞、检查点频繁、客户端发包碎片化等影响,需调优配置、禁用索引、使用二进制格式及流式导入。

为什么 COPY FROM 比 INSERT 快,但你没跑出理论速度?
COPY FROM 的底层绕过 SQL 解析、触发器和参数绑定,直接写入存储层,理论吞吐可达每秒数万行。但实际卡顿往往不是命令没用对,而是 WAL 写入阻塞、检查点频繁或客户端发包太碎。比如 synchronous_commit = on 会让每次写入都等 WAL 刷盘,checkpoint_timeout = 30s 会强制每半分钟做一次全量检查点,严重拖慢大导入。
- 确认
wal_level是replica(非logical),避免逻辑复制额外开销 - 把
checkpoint_timeout调到3600(1 小时),max_wal_size设为单次导入数据体积的 1.5 倍以上 - 临时设
synchronous_commit = off—— 仅限可接受崩溃丢失最后几秒数据的场景 - 禁用目标表上所有索引和外键约束,导入完成后再重建;否则每行插入都触发索引更新
COPY FROM STDIN 的客户端实操要点(以 psycopg2 为例)
很多人用 copy_expert() 拼字符串,结果被 Python GIL 和内存拷贝拖垮。真正高效的是 copy_from() 配合流式迭代器,它直接喂二进制或 CSV 行,不构造完整字符串。
- 用
csv.writer写入BytesIO,而非StringIO+ encode(),减少编码转换次数 - 每批次控制在 10000–50000 行;小于 1000 行网络往返放大延迟,大于 100000 行易触发客户端内存暴涨或超时
- 显式关闭
autocommit:conn.autocommit = False,COPY 本身是原子操作,外层事务反而增加开销 - 避免在循环里反复调用
copy_from();应一次性传入文件对象或生成器,让驱动内部流式处理
哪些 COPY 参数真正影响吞吐?别只盯着 DELIMITER
DELIMITER 和 NULL 是基础项,但真正卡性能的是几个“隐形开关”。比如加 HEADER 会让 PostgreSQL 多做一次整行解析,哪怕你 CSV 第一行确实是列名——预处理时删掉 header 更稳。
- 优先用
FORMAT binary:比csv快 2–3 倍,但需客户端支持二进制序列化(psycopg2 中配合Binary()包装) - 加
FREEZE:当目标表无并发写入时启用,跳过后续 VACUUM,减少 MVCC 开销;仅对本次全量插入生效 - 去掉
LOG ERRORS:错误日志本身会成为 I/O 瓶颈;先用小批量验证格式,再全量跑 - 明确指定
ENCODING 'UTF8',避免服务端自动探测失败导致中断
导入后三件事,漏一件前面全白干
COPY 不触发 ANALYZE,也不更新 pg_class.reltuples,统计信息滞后会让后续查询走错执行计划。autovacuum 默认延迟 1 分钟以上才启动,等不及。
- 立即执行
ANALYZE table_name,别依赖自动任务 - 如果表刚清空重建,考虑加
VACUUM (FULL, ANALYZE) table_name,重置页面状态并收集统计信息 - 检查
pg_stat_progress_copy视图确认是否真完成;有时客户端断连但服务端还在后台刷 WAL,状态显示 “done” 实际未落盘


















