pgloader 完全指南:一条命令把 CSV、SQLite、MySQL、MSSQL 等任意数据源迁移到 PostgreSQL
pgloader 完全指南:一条命令把 CSV、SQLite、MySQL、MSSQL 等任意数据源迁移到 PostgreSQL
pgloader 是一款面向 PostgreSQL 的数据装载与数据库迁移工具,其设计目标是让用户通过一条命令行(或一个 .load 命令文件)完成从文件或异构数据库到 PostgreSQL 的完整迁移,并支持边装载边转换数据、装载前后执行任意 SQL。读完本文,你将掌握 pgloader 支持的全部数据源与目标端、完整的特性矩阵,以及命令文件 DSL 与命令行两种驱动方式的核心用法,并能在 quickstart 与 command 等文档的配合下直接上手实战。
一、pgloader 是什么:基于 COPY 协议的流式装载器
根据 docs/intro.rst 的定位,pgloader 的核心能力可以概括为三点:
- 多源装载:从各种来源读取数据并灌入 PostgreSQL;
- 即时转换:对读取的数据在装载过程中“on the fly”进行转换;
- 前后置 SQL:在装载之前与装载之后提交原始 SQL。
在传输层,pgloader 使用 PostgreSQL 的 COPY 协议将数据流式送入服务器。这一设计在源码中有明确对应:src/pg-copy/copy-rows-in-stream.lisp 中的 stream-rows-to-copy 通过 cl-postgres:open-db-writer 打开数据库写入端,然后在事务中逐行 stream-row 把数据推入 COPY 流,完成后再 COMMIT;发生错误时则 ROLLBACK 并更新统计信息。也就是说,pgloader 不是把数据整理成 SQL 语句再执行,而是复用 PostgreSQL 原生的、最高效的 COPY 二进制/文本协议通道。
与直接使用 COPY 相比,pgloader 的独特价值在于错误管理:它会将出错的数据与正常数据分离开来,通过填充一对 reject.dat 与 reject.log 文件来管理错误。其实现位于 src/utils/reject.lisp:
reject.dat:以 PostgreSQL COPY TEXT 格式记录被拒绝的数据行(%process-bad-row中write-sequence写入原始行字节);reject.log:以 UTF-8 追加记录 PostgreSQL 拒绝这些数据时抛出的错误信息。
这种“好数据照常装载、坏数据单独留档”的事务行为,正是 pgloader 相比裸 COPY/\copy 以及 Foreign Data Wrapper 的核心优势:PostgreSQL 默认事务语义下,输入数据中任何一行错误都会中断整张表的批量装载,而 pgloader 会把错误的影响范围控制在单个批次内。
二、数据源矩阵:文件与数据库全覆盖
pgloader 能识别并读取多种数据来源,按大类可分为文件与数据库两类(详见 docs/intro.rst):
文件类(Files)
- CSV:逗号/制表符等分隔的文本文件,可配置表头、引号、转义、编码等;
- Fixed Format:定宽格式文件,通过字段长度描述来解析;
- Postgres COPY text format:PostgreSQL 自身的 COPY 文本格式(
copy源); - DBF:dBase 系列数据库文件,可附带 DBT memo 文件;
- IXF:IBM 集成交换格式(Integration Exchange Format)。
数据库类(Databases)
- SQLite(含自动发现表、索引、外键);
- MySQL / MariaDB;
- MS SQL Server;
- PostgreSQL(支持 PostgreSQL 到 PostgreSQL 的迁移);
- Redshift。
值得注意的细节:文件类源与数据库类源的驱动方式完全不同——文件源通常需要用户完整描述输入文件的字段属性(分隔符、列定义、编码等),而数据库源则由 pgloader 直接连接在线服务,自动获取元数据(表结构、索引、约束、类型定义),无需人工描述。这也是为什么从 SQLite 迁移一行命令即可完成,而装载一个裸 CSV 文件往往要给出 --field/--with 等参数。
在源码层面,src/api.lisp 中的 *get-code-for-source* 注册表把每种连接类型映射到对应的装载代码生成函数,涵盖 copy-connection、fixed-connection、csv-connection、dbf-connection、ixf-connection、sqlite-connection、mysql-connection、mssql-connection 与 pgsql-connection,与文档列出的数据源一一对应。
三、目标端:任何讲 PostgreSQL 协议的产品
pgloader 通过 PostgreSQL 协议向目标端写数据,因此凡是支持该协议的产品都可以作为目标:
- PostgreSQL:标准目标端;
- Citus:PostgreSQL 的分布式扩展,pgloader 支持把数据分布到 Citus(功能矩阵中的 “Distribute to Citus”);
- Redshift:Amazon Redshift 兼容 PostgreSQL 线协议,可作为目标端。
需要说明的是,以上目标端信息来自 docs/intro.rst,其中 Citus 相关实现可进一步参考 src/pgsql/pgsql-ddl-citus.lisp 与 clojure/src/pgloader/ddl/citus.clj。
四、功能矩阵:按源数据库的能力对照表
pgloader 对不同源数据库的自动化程度与功能支持并不一致。docs/intro.rst 给出了官方功能矩阵,这里完整整理为 Markdown 表格(✓ 表示支持,✗ 表示当前未实现但技术上可添加,空单元格表示该特性对对应源没有意义):
| 功能(Feature) | SQLite | MySQL | MS SQL | PostgreSQL | Redshift |
|---|---|---|---|---|---|
| One-command migration | ✓ | ✓ | ✓ | ✓ | ✓ |
| Continuous Migration | ✓ | ✓ | ✓ | ✓ | ✓ |
| Schema discovery | ✓ | ✓ | ✓ | ✓ | ✓ |
| Partial Migrations | ✓ | ✓ | ✓ | ✓ | ✓ |
| Schema only | ✓ | ✓ | ✓ | ✓ | ✓ |
| Data only | ✓ | ✓ | ✓ | ✓ | ✓ |
| Repeatable (DROP+CREATE) | ✓ | ✓ | ✓ | ✓ | ✓ |
| User defined casting rules | ✓ | ✓ | ✓ | ✓ | ✓ |
| Encoding Overrides | ✓ | ||||
| On error stop | ✓ | ✓ | ✓ | ✓ | ✓ |
| On error resume next | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pre/Post SQL commands | ✓ | ✓ | ✓ | ✓ | ✓ |
| Post-Schema SQL commands | ✗ | ✓ | ✓ | ✓ | ✓ |
| Primary key support | ✓ | ✓ | ✓ | ✓ | ✓ |
| Foreign key support | ✓ | ✓ | ✓ | ✓ | |
| Online ALTER schema | ✓ | ✓ | ✓ | ✓ | ✓ |
| Materialized views | ✗ | ✓ | ✓ | ✓ | ✓ |
| Distribute to Citus | ✗ | ✓ | ✓ | ✓ | ✓ |
关于矩阵的两点官方解读:
- 部分缺失支持只是因为该特性对其它源“没有需求”,而非技术障碍——例如只有 MySQL 需要 Encoding Overrides(覆盖表或列的编码元数据),因为列表中的源里 MySQL 是唯一无法完全保证文本编码的;
- 同理,Redshift 不支持外键,因此 Foreign key support 一列为空。
矩阵中“Continuous Migration(持续迁移)”特别值得一提:得益于数据库源可以直接在线读取,pgloader 支持无人值守的在线迁移(Live Migration),一次命令同时处理 schema 与数据两部分,从而实现持续迁移模式。
五、两种驱动方式:命令文件 DSL 与命令行直连
pgloader 实现了自己的 Command Language——一种用于描述数据装载与迁移全过程的 DSL。不过你并非总需要写命令文件,docs/intro.rst 给出了命令行接受的两种变体:
pgloader [<options>] [<command-file>]...
pgloader [<options>] SOURCE TARGET
- 第一种:提供一个或多个
.load命令文件,文件中用 pgloader 命令语言描述迁移规格; - 第二种:直接在命令行给出数据来源(Source)与 PostgreSQL 数据库连接的目标(Target)。
这一分支逻辑在源码 src/main.lisp 的 main 函数中清晰可见:当剩余参数数量为 2 时调用 process-source-and-target 走“SOURCE TARGET”路线;否则走 process-command-file 读取命令文件。而 src/api.lisp 的 process-source-and-target 会解析来源 URI、目标 URI,以及 --type、--encoding、--set、--with、--field、--cast、--before、--after 等命令行选项,最终调用统一的 load-data 入口。
命令文件的 DSL 语法在 docs/command.rst 中有完整定义,其主干结构如下:
LOAD <source-type>
FROM <source-url>
[ HAVING FIELDS <source-level-options> ]
INTO <postgresql-url>
[ TARGET TABLE [ "<schema>" ]."<table name>" ]
[ TARGET COLUMNS <columns-and-options> ]
[ WITH <load-options> ]
[ SET <postgresql-settings> ]
[ BEFORE LOAD [ DO <sql statements> | EXECUTE <sql file> ] ... ]
[ AFTER LOAD [ DO <sql statements> | EXECUTE <sql file> ] ... ]
;
核心子句为 LOAD、FROM、INTO、WITH;部分命令还实现 SET 子句,或 CAST 等源特有子句。DSL 解析器位于 src/parsers/command-parser.lisp,其 command 规则覆盖 load-archive、load-csv-file、load-fixed-cols-file、load-copy-file、load-dbf-file、load-ixf-file、load-pgsql-database、load-mysql-database、load-mssql-database、load-sqlite-database 等全部命令类型,命令之间以分号(;)分隔。
5.1 FROM:定义数据从哪来
FROM 子句指定数据来源,每种命令有自己的变体。例如 CSV 源支持 inline、stdin、普通文件名、带引号的文件名以及 FILENAME MATCHING 子句;而 MySQL 源只接受 MySQL 数据库 URI。
5.2 INTO:定义数据往哪去
PostgreSQL 连接 URI 中必须包含目标表名(可带 schema 限定),且目标表需要预先在 PostgreSQL 中创建。INTO 还支持可选的逗号分隔目标列列表,每列可以是输入字段名,也可以是“目标列名 + PostgreSQL 数据类型 + USING 表达式”的空白分隔组合:
USING表达式可以是任意合法的 Common Lisp 形式,求值时包上下文为pgloader.transforms,因此可以使用该包内定义的函数(包括通过--load动态加载的函数);- 每个
USING表达式在运行时被编译为原生代码; - 该机制允许 pgloader 把 CSV 中任意数量的字段投影为数据库中任意数量的列。
5.3 WITH:装载选项
WITH 子句提供一组选项,全局语法为 key = value、use option 或 do not use option 三种形式。所有数据源通用选项包括:
on error stop/on error resume nextbatch rows = Rbatch size = ... MBprefetch rows = ...
另有并行度相关设置:
workers = Wconcurrency = Cmax parallel create index = Ichunk size = N MB
5.4 SET:PostgreSQL 会话参数
SET 子句允许为 pgloader 打开的所有会话设置 session 参数,语法为参数名 + 等号 + 单引号包裹的值,多个参数用逗号分隔。参数名与取值 pgloader 不做校验,原样透传给 PostgreSQL。
5.5 BEFORE / AFTER LOAD:前后置 SQL
BEFORE LOAD DO:装载数据前执行 SQL,最常见的场景是CREATE TABLE IF NOT EXISTS。每条命令必须用美元符($$)包裹(dollar-quoted),多条查询以逗号分隔;BEFORE LOAD EXECUTE:同上,但从 SQL 文件读取查询,支持 PostgreSQL dollar-quoting 以及\i、\ir包含指令(同psql批处理模式);AFTER LOAD DO/AFTER LOAD EXECUTE:装载完成后执行,是创建索引、约束或重新启用触发器的恰当时机;AFTER CREATE SCHEMA DO/AFTER CREATE SCHEMA EXECUTE:schema 由 pgloader 创建之后、数据装载之前执行,适合做ALTER TABLE、自定义扩展(如分区)等。
六、命令行选项速览
除了命令文件,pgloader 也支持完全从命令行驱动一次装载。常用选项(完整定义见 docs/pgloader.rst,实现见 src/main.lisp 的 *opt-spec*):
查询类(不装载数据)
--help:显示用法后退出;--version:显示版本后退出;--list-encodings:列出本版本已知的编码;--upgrade-config:把 2.x 时代的pgloader.conf(INI 语法)转换为新的命令语法输出到标准输出。
通用类
--verbose/--quiet/--debug:控制日志详细程度;--root-dir:输出根目录(默认/tmp/pgloader),reject 文件、日志等都在其下;--logfile:日志文件路径(默认/tmp/pgloader/pgloader.log);--log-min-messages/--client-min-messages:分别过滤写入日志文件与输出到控制台的消息级别;--summary:把汇总输出复制到指定文件,扩展名为.csv、.copy或.json时输出对应机器可读格式;--dry-run:只检查连接、不真正装载数据,适合调试.load文件;--on-error-stop:一旦 PostgreSQL 拒绝任何数据就停止,便于调试转换函数与类型转换规则;--no-ssl-cert-verification:接受本地签发的服务端证书,绕开SSL verify error: 20 X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY(正确做法仍是使用受信证书)。
命令行专用类(配合 SOURCE TARGET 使用)
--with <option>:等价于命令文件的WITH子句,可多次使用;--set "guc_name='value'":设置 PostgreSQL 会话参数,值的单引号不可省略;--field <spec>:定义源文件字段,可逐字段多次使用,也可用逗号分隔多个字段定义(等价于HAVING FIELDS子句);--cast <rule>:设置特定的类型转换规则;--type <csv|fixed|db3|ixf|sqlite|mysql|mssql>:当SOURCE无法被自动识别时强制指定源类型;--encoding <encoding>:指定源文件编码;--before <file>/--after <file>:从 SQL 文件中解析查询并在装载前/后执行(查询需以分号结尾,支持\i、\ir包含)。
6.1 连接串格式
文件类来源连接串形如:
format:///absolute/path/to/file.ext
format://./relative/path/to/file.ext
其中 format 可取 csv、fixed、copy、dbf、db3、ixf;数据库类来源形如:
db://user:pass@host:port/dbname
其中 db 可取 sqlite、mysql、mssql。对于文件类源,pgloader 还原生支持从 HTTP 位置取文件并在需要时解压归档,但此时必须用 --type 显式指定文件格式(详见 docs/pgloader.rst)。
目标端连接串使用 PostgreSQL 连接 URI 格式:
postgresql://[user[:password]@][netloc][:port][/dbname][?option=value&...]
各组成部分的规则(docs/command.rst):
- user:可含任意字符,冒号需双写(
::),@需双写(@@);省略时依次回退到PGUSER、USER环境变量; - password:可含任意字符,
@需双写(@@);省略时回退到PGPASSWORD环境变量,仍未设置则按libpq规则查找.pgpass文件(可用PGPASSFILE覆盖路径,Unix 下默认为~/.pgpass); - netloc:可以是域名、IPv4 或 Unix 域套接字路径;可通过
unix:/path:port/dbname强制指定套接字路径与端口,路径中的冒号双写即可转义; - dbname:应为合法标识符;省略时回退到
PGDATABASE,再回退到用户名; - options:
name=value形式、&分隔;支持tablename(可带 schema 限定)、sslmode、host、port、dbname、user、password;sslmode可取disable、allow、prefer、require、verify-full。为向后兼容,tablename也可直接写在 URI 的?之后而不带tablename=前缀。选项会覆盖 URI 主组件。
七、快速上手:三条最典型的命令
结合 docs/quickstart.rst,以下三条命令展示了“一条命令完成迁移”的实战形态。
从 SQLite 全量迁移(自动发现表、索引、外键,类型转换后迁移数据):
createdb newdb
pgloader ./test/sqlite/sqlite.db postgresql:///newdb
从 MySQL 迁移(含 schema、数据,单行命令完成):
createdb pagila
pgloader mysql://user@localhost/sakila postgresql:///pagila
从 CSV 文件装载到已存在的表(全部参数走命令行):
pgloader --type csv \
--field id --field field \
--with truncate \
--with "fields terminated by ','" \
./test/data/matching-1.csv \
postgres:///pgloader?tablename=matching
注意 CSV 示例中:由于没有命令文件,--type、--field、--with 等附加信息必须全部在命令行给出;目标表必须已存在,且 PostgreSQL URI 通过 ?tablename= 携带目标表名(源数据见 test/data/matching-1.csv)。
其他实用变体:
- 从标准输入读取:源用
-表示 stdin,可配合 Unix 管道流式装载,例如gunzip -c source.gz | pgloader --type csv ... - pgsql:///target?foo; - HTTP 远程 CSV:直接把 URL 作为来源传给 pgloader,同时配合
--field "usps,geoid,..."与--with "skip header = 1"等选项; - 流式处理 HTTP 压缩文件:
curl <url.gz> | gunzip -c | pgloader --type csv ... - postgresql:///pgloader?districts_longlat——pgloader 默认会先把 HTTP 内容下载到本地再解压处理,而管道方式让操作系统负责网络与命令间的流式缓冲,pgloader 只负责把数据流式灌入 PostgreSQL; - 远程归档 DBF:
pgloader --type dbf <http-url-of-zip> postgresql:///foo,pgloader 会先下载、再解压、然后自动发现 schema 并装载数据——此时无法自动猜测源类型,必须显式--type dbf。
八、深入原理:批处理、错误恢复与并行模型
为了在享受 COPY 速度的同时规避其“全有或全无”的缺陷,pgloader 把数据切成批次处理(详见 docs/batches.rst):
- 默认每个批次 25000 行,批次在内存中保留,以便出错时重试;
- 当 PostgreSQL 拒绝整个批次时,pgloader 解析失败 COPY 的
CONTEXT错误信息(其中包含批内行号,如CONTEXT: COPY errors, line 3, column b: "2006-13-11"),据此重试更小的批次:先重载错误行之前的所有行,记录错误行为 rejected,再尝试装载剩余部分,直到找出全部坏行; - 装载结束时,
root-dir下会生成以目标数据库命名的目录,其中以目标表名为文件名、.dat/.log为扩展名的一对文件分别保存被拒数据(PostgreSQL COPY TEXT 格式)与客户端侧日志。
on error stop 与 on error resume next 控制是否启用批处理错误恢复:文件类装载(CSV、IXF、DBF)默认 on error resume next;而从其它 RDBMS 迁移时通常希望过程可复现,因此 SQLite、MySQL、MS SQL 源默认 on error stop,便于先修正 cast 规则、转换函数或输入数据,直到迁移完整跑通。
性能与并行模型:pgloader 采用经典的 Unix 管道模型——一个读取线程负责读数据(读 CSV、查 MySQL 等)并把预处理后的数据推入队列,另一个写入线程从队列取出数据、应用变换、以 COPY 协议批量写入 PostgreSQL。在此基础上:
workers控制同时复制多少张表:数据库源默认 4,文件源默认 8;concurrency控制单表内的并行度:即每张表启动多少条独立的“读→写”流水线,默认 1;要求大于 1 且源支持范围分区(MySQL 按主键范围,PostgreSQL 14+ 按ctid块范围),每个并行 reader 配专属 writer,按chunk size(默认 50 MB)切分不相交的源表区间;max parallel create index限制并行CREATE INDEX线程数(默认按源 schema 中单表最大索引数),这些线程只等待 PostgreSQL 完成实际工作,不计入 workers/concurrency 配额。
文档也坦诚指出:若给的是 PostgreSQL COPY 命令本身就能解析、且不含坏数据的文件,pgloader 永远不会比直接使用 COPY 更快——它的价值在于转换、容错与自动化,而非在单文件最佳场景下挑战裸 COPY 的极限速度。
九、进阶特性一览
- 模板化命令(Mustache):命令文件支持 Mustache 模板系统,可通过 OS 环境变量注入变量,例如
DBPATH=sqlite/sqlite.db pgloader ./test/sqlite-env.load配合from '{{DBPATH}}'使用;也可通过--context ./test/sqlite.ini把 INI 文件中的键值作为模板上下文(docs/command.rst); - 正则表达式:若干子句接受正则,规则为以
~开头、后随成对分隔符(~//、~[]、~{}、~()、~<>、~""、~''、~||、~##),选择不与表达式冲突的分隔符对; - 注释:命令中支持
--行注释与/* */块注释,任何允许空白的位置都允许注释; - 类型转换规则(CAST):通过
--cast或命令文件CAST子句定义用户自己的类型转换规则,是数据库迁移正确性的关键开关。
结语
从 docs/intro.rst 这张“总纲”出发可以看到:pgloader 的设计哲学是“一个命令、多源输入、协议直通、错误隔离”。文件源靠用户描述、数据库源靠自动发现,目标端统一走 PostgreSQL 协议;装载过程可转换、可批处理、可并行、可重试。配合仓库内的 quickstart、command、batches、pgloader 等文档,以及 src/api.lisp、src/main.lisp 等核心实现,无论是简单的 CSV 灌库还是复杂的 MySQL 全量迁移,你都能用一条命令或一个 .load 文件稳定落地。