还在用
CREATE DATABASE拷半天数据?PG 18 让你秒建一个完整的库副本,零额外磁盘。
如果你管过 PostgreSQL 数据库集群,下面这个场景你一定不陌生:
「给我搭一套测试环境,要跟线上一样的数据量。」
你的第一反应是什么?
—— pg_dump 导出一小时,pg_restore 导入又一小时。一个 200GB 的库,光拷贝就耗掉半天。等环境就绪,开发已经自己拿 navicat 连上生产库了……
这是 DBA 的日常,也是数据库分支管理至今最大的痛点。
而 PostgreSQL 18 给了我们一个全新的答案:CLONE。
PG 18 新增了一个 GUC 参数:
file_copy_method = clone | copy默认值是 copy——老老实实逐字节拷贝。而 clone 则使用操作系统的 Copy-on-Write(写时复制) 机制,底层调用的系统调用是:
copy_file_range()copyfile()写时复制是什么意思?打个比方:
你有一本厚书,同事说「借我看看」。COPY 模式:你花半小时复印一整本给他。CLONE 模式:你把书往桌上一放,「看吧,咱俩一起看。你要划线写字的地方,我再给你撕页重印。」
共享物理块,修改时才复制。 这就是 CLONE 的精髓。
为了让下面的实验可复现,先用 IvorySQL 在线试用环境(trial.ivorysql.org)准备基础设施。
# 高速业务存储
mkdir-p /opt/tbs_fast && chown postgres:postgres /opt/tbs_fast
# 归档历史存储
mkdir-p /opt/tbs_archive && chown postgres:postgres /opt/tbs_archive-- 高速表空间
CREATETABLESPACE tbs_fast LOCATION '/opt/tbs_fast';
-- 归档表空间
CREATETABLESPACE tbs_archive LOCATION '/opt/tbs_archive';
GRANTCREATEONTABLESPACE tbs_fast, tbs_archive TO postgres;-- 主干库,默认 pg_default 表空间
CREATEDATABASE db_template_main;
\c db_template_main
-- 业务表(默认表空间,会随库迁移)
CREATETABLE biz_base(id bigintprimarykey, info text);
-- 字典表(固定在 tbs_archive,不受整库迁移影响)
CREATETABLE dict_static(codeint, name text)TABLESPACE tbs_archive;
-- 灌入 1 万行基础数据
INSERTINTO biz_base SELECT generate_series(1,10000),'low data';假如你要把整个库从 HDD 迁到 SSD,传统的做法是什么?
数据量小还好,几 TB 的库意味着好几小时的 I/O 压力。
-- 强制终止
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname ='db_template_main';-- 当前会话启用 COW 克隆
SET file_copy_method ='clone';
-- 极速迁移至 SSD 表空间
ALTERDATABASE db_template_main SETTABLESPACE tbs_fast;注意:文件系统需支持 reflink(XFS、Btrfs、ZFS 表现最佳),ext4 可能降级为 COPY。
SET file_copy_method ='copy';
ALTERDATABASE db_template_main SETTABLESPACE tbs_fast;biz_base:物理文件从 pg_default 移动到 tbs_fastdict_static:绑定在 tbs_archive,纹丝不动(手动指定表空间不受整库迁移影响)tbs_fast-- 1. 确认默认表空间切换
SELECT datname, spcname AS db_default_tablespace
FROM pg_database d
JOIN pg_tablespace ts ON d.dattablespace= ts.oid
WHERE datname ='db_template_main';
-- 2. 查看表关联表空间
\c db_template_main
SELECT relname, spcname
FROM pg_class c
LEFTJOIN pg_tablespace ts ON c.reltablespace= ts.oid
WHERE relname IN('biz_base','dict_static');
-- 3. 底层物理文件路径(最权威)
SELECT relname, pg_relation_filepath(c.oid)AS file_path
FROM pg_class c
WHERE relname IN('biz_base','dict_static');这才是 CLONE 真正闪光的场景——像 Git 分支一样管理数据库版本。
SET file_copy_method = 'clone';
-- 创建开发分支
CREATE DATABASE db_v1_test
TEMPLATE db_template_main
STRATEGY = FILE_COPY;
-- 创建预发布分支
CREATE DATABASE db_v2_pre
TEMPLATE db_template_main
STRATEGY = FILE_COPY;验证:
$ psql -d db_template_main -c "SELECT oid, datname FROM pg_database WHERE datname IN ('db_template_main','db_v1_test');"
oid | datname
-------+------------------
16386 | db_template_main
16400 | db_v1_test
# 对比文件列表——完全一致,仅 inode 不同
ls -1 /opt/tbs_fast/PG_18_202506291/16386 | sort > /tmp/tmp.list
ls -1 /opt/tbs_fast/PG_18_202506291/16400 | sort > /tmp/clone.list
diff /tmp/tmp.list /tmp/clone.list # ← 无输出,文件清单完全相同目录结构、文件名、文件大小完全相同,只有 inode 不同——这就是 reflink 的特征。
SET file_copy_method = 'copy';
CREATE DATABASE db_prod_backup
TEMPLATE db_template_main
STRATEGY = FILE_COPY;每个文件全新拷贝,inode 全部不同,适合生产备份与离线归档。
# COW 克隆默认开启,系统不支持时自动降级 COPY
file_copy_method = cloneSELECT pg_reload_conf();
SHOW file_copy_method; -- 确认生效看到这里,你一定会想:
能不能像 Git 一样,创建分支 → 开发 → 合并回主线?
我的回答是:CLONE 提供了分支的「创建能力」,但「合并」要自己搭桥。
能力 | 评价 |
|---|---|
创建快照分支 | ✅ 秒级,零额外空间 |
独立开发/测试 | ✅ 完全可写,COW 隔离 |
并行多版本 | ✅ 磁盘零增长 |
删除分支 | ✅ DROP DATABASE |
能力 | 现状 | 解决方案 |
|---|---|---|
DDL 合并(结构变更) | ❌ 无原生支持 | Flyway / Liquibase / sqldef |
DML 合并(数据变更) | ❌ 无原生支持 | 逻辑复制 / MERGE SQL / pg_dump |
冲突检测 | ❌ 无原生支持 | 乐观锁(版本号字段)+ 应用层 |
分支历史 | ❌ 无原生支持 | 外部工具记录 |
-- 将分支数据合并回主线
MERGE INTO biz_base t
USING dblink('dbname=db_v1_test', 'SELECT id, info FROM biz_base') s(id, info)
ON t.id = s.id
WHEN MATCHED AND t.info IS DISTINCT FROM s.info
THEN UPDATE SET info = s.info, updated_at = now()
WHEN NOT MATCHED
THEN INSERT (id, info) VALUES (s.id, s.info);外层编排工具(pg_branch.py)
├── CLONE → 创建快照分支 (PG 18 原生)
├── DDL diff + apply (迁移工具)
├── 逻辑复制 / MERGE (DML 同步)
└── DROP DATABASE (清理)CLONE 是基石,不是全部。 但它补上了数据库分支管理最后一块拼图——让创建分支的成本从「小时级」降到「秒级」。