COPY命令是绕过SQL引擎的底层数据通道,直接写入存储层,跳过解析、计划、触发器和逐行约束检查;而INSERT需走完整执行流程,百万行数据性能差距可达几十倍。

COPY 命令不是“更快的 INSERT”,它是绕过 SQL 引擎的底层数据通道。如果你还在用 INSERT 循环插百万行,或者拼接多值 INSERT INTO t VALUES (...), (...), ...,那性能差距不是几倍——是几十倍,且 WAL 日志、CPU 和网络开销都会明显超标。
为什么 COPY FROM 比 INSERT 快,但你可能没跑出理论速度
根本原因不是“语法更短”,而是执行路径完全不同:
-
COPY FROM直接把字节流写入存储层,跳过 SQL 解析、查询计划生成、触发器、约束检查(除非显式启用) -
INSERT每一行都要走完整执行引擎,哪怕用了多值语法,仍需逐行校验类型、索引更新、WAL 记录粒度更细 - 真实瓶颈常不在 COPY 本身,而在 WAL 配置:比如
checkpoint_timeout设为 30 秒,COPY 过程中频繁触发检查点,会卡住写入 - 默认
synchronous_commit = on会让每次 WAL 刷盘等待磁盘确认,对单次大批量导入属于过度保护
实测中,100 万行 CSV 导入,COPY 耗时 8.4 秒;若未调优 WAL 参数,可能拖到 40 秒以上——快不快,取决于你怎么配,不是用不用。
COPY FROM 的两种写法:服务器端 vs 客户端权限怎么选
别混淆 COPY 和 \copy,它们访问文件的位置和权限模型完全隔离:
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
-
COPY users FROM '/var/data/users.csv':PostgreSQL 服务进程(通常是postgres系统用户)必须能读取该路径,数据库管理员才能执行 -
\copy users FROM 'users.csv':psql 客户端从本地机器读文件,走的是你的当前用户权限,普通用户也能用,但性能略低(数据需经客户端中转) - 生产环境批量导入首选
COPY,但要注意:Docker 容器里 PostgreSQL 无法直接读宿主机路径,得把文件先cp进容器或挂载卷 - 云数据库(如 RDS)通常禁用
COPY的文件路径访问,只能用\copy或COPY FROM STDIN+ 应用层流式推送
COPY FROM STDIN 实战:应用代码里怎么喂数据才不卡
当无法用文件路径时(比如 Web 后端接收上传的 CSV),COPY FROM STDIN 是唯一选择,但它对客户端缓冲和服务器参数更敏感:
- 不要一次性把整个 2GB 文件读进内存再传——用流式分块,每 10–50MB 提交一次
COPY子事务(注意:COPY本身是单事务,大块失败要重来) - 务必加
WITH (FORMAT binary):二进制格式比 CSV 少解析开销,类型转换零误差,尤其含时间、JSON、数组字段时更稳 - 禁用
HEADER(STDIN 不支持)、避免NULL 'null'这类字符串映射,改用\N表示空值,和二进制格式天然兼容 - Go 用
pq.CopyIn、Python 用cursor.copy_from()或copy_expert(),别手写stdin.write()—— 驱动已做缓冲优化
容易被忽略的冻结与索引陷阱
快只是第一步,入库后查不出、Vacuum 报警、索引膨胀才是隐形雷:
- 大表导入后立即
VACUUM ANALYZE,否则后续查询可能走错执行计划;如果导入前表为空,可加FREEZE选项跳过事务 ID 标记:COPY t FROM ... WITH (FREEZE true) - 导入前手动
DROP INDEX,导入完再CREATE INDEX,比边插边维护索引快 3–5 倍;唯一索引不影响,但普通 B-tree 索引强烈建议卸载 -
DISABLE TRIGGER仅对用户定义触发器有效,系统触发器(如外键检查)无法绕过;真要跳过约束,得临时SET CONSTRAINTS ALL DEFERRED,但风险自担
最常被跳过的动作是:没关 autovacuum 临时表、没预估好 work_mem 导致排序溢出到磁盘、导完忘了重建统计信息——这些不会让 COPY 变慢,但会让之后的所有查询变慢。

















