PostgreSQL COPY 批量导入为什么快?使用时要注意什么?
简化版
COPY 是 PostgreSQL 的高效批量导入导出命令,比逐条 INSERT 少大量 SQL 解析、网络往返和事务开销。使用时要注意数据格式、约束和索引成本、事务大小、错误处理、权限以及导入后统计信息更新。
详细版
逐条插入 100 万行会产生 100 万次 SQL 执行和大量网络交互;COPY 可以一次流式导入,适合初始化、ETL、日志落库和批量迁移。
常见用法:
COPY users(id, name, email)
FROM '/tmp/users.csv'
WITH (FORMAT csv, HEADER true);
也可以用客户端侧 \copy 避免服务端文件权限问题。导入大表时要评估索引维护、外键检查、WAL 量、锁和 autovacuum 影响。
完整版教学
一、COPY 为什么比 INSERT 快
逐条 INSERT 的成本不只是写数据,还包括 SQL 解析、计划、网络往返、事务提交和索引维护。100 万行逐条插入,这些固定成本会重复 100 万次。
COPY 把数据当作流导入,数据库可以用更紧凑的路径处理批量数据,减少重复开销。
逐条 INSERT:请求 -> 解析 -> 执行 -> 返回 × 1,000,000
COPY:建立流 -> 连续读取数据 -> 批量写入
二、基本用法和 \copy 区别
服务端 COPY FROM '/path/file.csv' 读取的是数据库服务器上的文件,需要数据库进程有文件权限。
客户端 \copy 是 psql 命令,从客户端读取文件,再通过连接传给服务端。它更适合本地导入。
\copy users(id, name, email) FROM 'users.csv' WITH (FORMAT csv, HEADER true)
面试中说出这个区别,能避免“为什么我本地文件 COPY 不到”的常见坑。
三、索引和约束会影响导入速度
导入时每插入一行,都可能维护索引、检查唯一约束、检查外键。索引越多,导入越慢。
如果是空表初始化,常见做法是先导入数据,再创建索引。因为批量建索引通常比边插边维护更高效。
| 场景 | 推荐方式 |
|---|---|
| 空表初始化 | 先 COPY,再建索引 |
| 在线增量导入 | 保留必要约束 |
| 临时中转 | 先导入 staging 表 |
| 脏数据多 | 先落临时表校验 |
四、事务和错误处理要设计
COPY 可以放在事务里。事务太大时,失败回滚成本和 WAL 压力会很高;事务太小又会增加提交开销。
例如导入 1000 万行,可以按文件切片或按批次处理。每批 10 万行,失败时只重跑当前批,而不是回滚全部。
脏数据场景不要直接导入核心表。先导入 staging 表,再用 SQL 校验、清洗、转换,最后写入正式表。
五、导入后为什么要 ANALYZE
大量导入后,表的数据分布变化明显。如果统计信息没更新,优化器可能还按旧数据估算,导致查询计划不准。
ANALYZE users;
对于新导入的大表,导入后执行 ANALYZE 是非常常见的收尾动作。否则数据已经进来了,查询却可能跑得很差。
六、常见误区与追问
- 误区:COPY 只是 INSERT 的语法简写。 它是专门的批量导入路径,减少大量重复开销。
- 误区:COPY 一定直接导入正式表。 脏数据和复杂转换场景应先进入 staging 表。
- 误区:导入后不用管统计信息。 大量数据变化后应
ANALYZE。 - 追问:COPY 和 \copy 区别?
COPY读服务端文件,\copy读客户端文件并发送。 - 追问:如何提高空表导入速度? 先导入数据,再建索引和约束,并合理控制事务批次。
七、加强记忆
记忆钩子:COPY 像整车卸货,逐条 INSERT 像一件一件扫码进仓;快是快,但仓库规则、质检和入库后盘点不能省。
回答这题要把速度来源、服务端/客户端文件、索引约束、事务批次、staging 表和 ANALYZE 都讲到。这样是完整的批量导入方案。