一、迁移环境及操作原则
| 项目 | 源库(生产) | 目标库(UAT) |
|---|---|---|
| PostgreSQL 版本 | 18.4 | 18.4 |
| 主库 IP(示例) | 192.0.2.10 | 192.0.2.20 |
| 备库 IP(示例) | --- | 192.0.2.21 |
| 数据库名称 | appdb | appdb |
| 业务账号 | appuser | appuser |
| 迁移方式 | pg_dump 备份 | pg_restore 恢复 |
迁移原则:
-
使用
pg_dump -Fc导出完整业务数据库。 -
目标库提前安装与源库兼容的 PostgreSQL 版本及扩展。
-
提前创建目标库需要的数据库角色,尽量保留原始属主和授权。
-
只在目标主库恢复一次;已有物理备库通过流复制同步。
-
生产数据库只执行查询及备份,不进行数据修改。
以下使用示例 IP、数据库名及账号。实际操作时按环境替换。
二、源数据库检查
执行位置:源库主服务器
切换 PostgreSQL 用户:
su - postgres
psql --version
pg_dump --version
连接数据库:
psql -X -d appdb
1. 查询数据库版本、字符集及排序规则
SHOW server_version;
SELECT
datname,
pg_encoding_to_char(encoding) AS encoding,
datcollate,
datctype,
datlocprovider,
pg_get_userbyid(datdba) AS db_owner
FROM pg_database
WHERE datname = current_database();
2. 查询源库已使用的扩展及版本
SELECT extname, extversion
FROM pg_extension
ORDER BY extname;
重点: 保存查询结果,目标主库和备库必须提前准备好所需扩展软件及兼容版本。
本次迁移使用了 PostGIS、TimescaleDB、Apache AGE、pglogical 等扩展。具体安装过程不在本文展开。
3. 查询数据库大小及表数量
SELECT pg_size_pretty(pg_database_size(current_database()));
SELECT schemaname, COUNT(*) AS table_count
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog','information_schema')
AND schemaname NOT LIKE 'pg_toast%'
GROUP BY schemaname
ORDER BY schemaname;
4. 检查业务账号与对象属主
SELECT rolname, rolcanlogin, rolsuper
FROM pg_roles
WHERE rolname = 'appuser';
SELECT n.nspname AS schema_name,
pg_get_userbyid(c.relowner) AS owner_name,
COUNT(*) AS table_count
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r','p')
AND n.nspname NOT LIKE 'pg_%'
GROUP BY n.nspname, c.relowner
ORDER BY 1,2;
记录业务账号和对象属主。如果还有其他对象所有者或 ACL 授权涉及的角色,也需提前准备。
退出:
\q
三、目标数据库提前准备
执行位置:目标主库
1. 检查数据库及扩展
su - postgres
psql --version
进入数据库:
psql -X -d postgres
执行:
SHOW server_version;
SHOW shared_preload_libraries;
SELECT name, default_version
FROM pg_available_extensions
ORDER BY name;
确认:
-
PostgreSQL 版本符合迁移要求。
-
源库所需扩展均已安装且版本兼容。
-
TimescaleDB 等需要预加载的扩展已正确配置并生效。
-
目标主库、备库均具备相应扩展软件文件。
2. 创建业务账号
在目标实例中提前创建源库所需角色。
CREATE ROLE appuser LOGIN;
设置目标环境密码:
\password appuser
其他被备份对象引用的角色也需要提前创建,避免恢复过程中出现 role does not exist。
3. 创建目标数据库
以下为本次迁移所用 UTF8、zh_CN.utf8 环境的示例:
CREATE DATABASE appdb
WITH
OWNER = postgres
ENCODING = 'UTF8'
LC_COLLATE = 'zh_CN.utf8'
LC_CTYPE = 'zh_CN.utf8'
TEMPLATE = template0;
注意: 字符集、Locale Provider、排序规则和数据库属主应以源库查询结果为准。以上命令仅适用于对应的 libc Locale 环境;使用 ICU 等其他配置时必须调整。
至此,目标环境应具备:
-
空的目标业务数据库。
-
所需业务角色。
-
所需扩展和依赖。
-
足够的数据库存储空间。
四、源库执行 pg_dump 全库备份
执行位置:源库主服务器
mkdir -p /app/pg_dump
DB=appdb
DUMP="/app/pg_dump/${DB}_$(date +%Y%m%d_%H%M%S).dump"
pg_dump \
-U postgres \
-d "$DB" \
-Fc \
-v \
-f "$DUMP" \
2>"${DUMP}.log"
echo "BACKUP_EXIT_CODE=$?"
ls -lh "$DUMP"
正常结果:
BACKUP_EXIT_CODE=0
确认归档可以读取:
pg_restore -l "$DUMP" | head -20
计算文件校验值:
sha256sum "$DUMP"
关键说明:
-
-Fc:自定义格式备份,供pg_restore使用。 -
备份的是
appdb整个数据库,包括业务表、数据、索引、视图、函数等数据库对象。 -
pg_dump不备份实例级角色和密码。 -
正常备份无需停止生产数据库,建议业务低峰期执行。
五、传输备份文件
首先在目标服务器准备目录并保证接收账号具有写入权限:
mkdir -p /app/pg_dump
在源库服务器执行:
scp -P 22 "$DUMP" \
postgres@192.0.2.20:/app/pg_dump/
22 为示例 SSH 端口,按实际环境修改。
传输完成后,在目标服务器执行:
ls -lh /app/pg_dump/
sha256sum /app/pg_dump/appdb_YYYYMMDD_HHMMSS.dump
与源库 SHA256 对比,确认文件一致。
六、目标库执行 pg_restore 整库恢复
执行位置:目标主库,postgres 用户
1. 本次使用 TimescaleDB,先进入恢复模式
psql -X -v ON_ERROR_STOP=1 -d appdb \
-c "CREATE EXTENSION IF NOT EXISTS timescaledb;"
psql -X -v ON_ERROR_STOP=1 -d appdb \
-c "SELECT timescaledb_pre_restore();"
未使用 TimescaleDB 的普通 PostgreSQL 数据库,不需要执行 TimescaleDB 专用步骤。
2. 正式恢复完整数据库
将 DUMP 替换成目标服务器上的真实文件名。
DB=appdb
DUMP=/app/pg_dump/appdb_YYYYMMDD_HHMMSS.dump
LOG=/tmp/appdb_restore.log
pg_restore \
-U postgres \
-d "$DB" \
--single-transaction \
--exit-on-error \
-v "$DUMP" >"$LOG" 2>&1
RC=$?
echo "RESTORE_EXIT_CODE=$RC"
if [ "$RC" -ne 0 ]; then
grep -n -m 1 -A 5 'ERROR:' "$LOG"
fi
tail -20 "$LOG"
3. 退出 TimescaleDB 恢复模式
只要前面成功进入了 TimescaleDB 恢复模式,无论数据恢复是否成功,都需要执行后处理:
psql -X -v ON_ERROR_STOP=1 -d appdb \
-c "SELECT timescaledb_post_restore();"
恢复成功的判断标准:
RESTORE_EXIT_CODE=0
同时确认 TimescaleDB 后处理成功、恢复日志不存在导致中断的错误。
关于恢复参数的重要经验
本次实际迁移曾使用:
--no-owner --no-acl
结果数据虽然恢复成功,但是还需要重新处理业务表属主、Schema 授权及序列权限。
以后为了缩短迁移时间,建议提前准备源库所需角色,优先采用上面的恢复命令,不加 --no-owner --no-acl**,保留原始对象属主和 ACL。**
只有明确决定重新规划目标库权限时,才考虑使用这两个参数。
如果恢复报错:
ERROR: role "appuser" does not exist
先在目标实例创建对应角色,再重新恢复。使用 --single-transaction 时,失败恢复事务中的修改会回滚,但事务之外预先创建的数据库、扩展和角色仍会保留。
七、恢复完成后快速验收
1. 检查扩展
SELECT extname, extversion
FROM pg_extension
ORDER BY extname;
与源库检查结果对比。
2. 检查业务表数量
SELECT schemaname, COUNT(*) AS table_count
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog','information_schema')
AND schemaname NOT LIKE 'pg_toast%'
GROUP BY schemaname
ORDER BY schemaname;
与源库备份前结果对比。
3. 检查 TimescaleDB 超表
仅使用 TimescaleDB 时执行:
SELECT hypertable_schema, hypertable_name
FROM timescaledb_information.hypertables
ORDER BY 1,2;
4. 检查业务账号登录
在目标服务器执行:
psql -h 192.0.2.20 -p 5432 \
-U appuser -d appdb -W
连接成功后:
SELECT current_user, current_database();
再使用业务账号抽查业务表和必要的函数、视图及读写权限。
5. 检查主备复制(存在备库时)
主库执行:
SELECT application_name, client_addr, state, sync_state
FROM pg_stat_replication;
正常应显示备库为 streaming。
备库执行:
SELECT pg_is_in_recovery();
正常物理备库返回 t。
6. 插入测试记录验证主备数据同步
仅在目标 UAT 环境执行。
主库创建测试表:
CREATE TABLE public.sync (
id BIGINT PRIMARY KEY,
ts TIMESTAMPTZ DEFAULT clock_timestamp()
);
主库插入:
INSERT INTO public.sync (id) VALUES (1);
备库查询:
SELECT * FROM public.sync ORDER BY id DESC;
备库能够查询到刚才插入的记录,即该次数据同步验证成功。
表可以保留,后续检查只需要插入新的 ID 再到备库查询。
八、迁移完成检查清单
-
源库和目标库 PostgreSQL 版本已核对。
-
源库扩展、字符集和 Locale 已核对。
-
目标扩展及所需角色提前准备完成。
-
pg_dump备份成功,退出码为 0。 -
备份文件传输成功,SHA256 一致。
-
pg_restore恢复成功,退出码为 0。 -
扩展、Schema、表、关键数据及特殊对象检查通过。
-
业务账号登录、权限及业务 SQL 验证通过。
-
主备复制及测试数据同步验证通过(如适用)。
九、总结
PostgreSQL 单数据库整库迁移的核心流程:
源库检查 → 目标准备 → pg_dump 备份 → 传输校验 → pg_restore 恢复 → 权限检查 → 业务及主备验收。
本次实际迁移最值得保留的经验只有三点:
-
先查扩展。 目标库提前安装对应扩展,不要等恢复报错再处理。
-
先建角色。 尽量保留源库属主和权限,避免恢复完成后大量手工授权。
-
恢复后验证。
pg_restore成功只是第一步,还需确认业务账号、数据对象和主备同步正常。
这套流程适用于版本兼容、目标环境已准备好的 PostgreSQL 单数据库逻辑整库迁移。实际执行前仍需根据数据量、资源负载和业务一致性要求确定备份及恢复时间窗口。