PostgreSQL 复制数据库时报源数据库正在被其他用户访问
CREATE DATABASE TEMPLATE 和 createdb -T 都要求源库没有其他连接。业务需要继续运行时,通过 pg_dump 和 pg_restore 制作测试副本,并核对权限、恢复事务和目标环境。
想把正在运行的业务库复制成测试库,执行模板复制后收到:
ERROR: source database "app_prod" is being accessed by other users
DETAIL: There are 5 other sessions using the database.
把 SQL 换成 createdb -T app_prod 仍然失败,因为两者使用同一种模板复制机制。它要求开始复制时,源数据库没有其他连接;复制期间还会阻止新连接进入源库。业务仍在访问源库时,重试命令或换一个目标库名都不会消除这项限制。
如果目标是在业务继续运行时得到一份测试数据,可以改用 pg_dump 导出,再用 pg_restore 恢复到独立测试库。下面按当前 PostgreSQL 客户端的自定义归档格式说明流程,源端、目标端和客户端优先使用相同主版本。它适合制作单库逻辑副本,不等于复制整个实例,也不适合作为持续同步方案。
确认报错指向的源库和连接
先看失败语句中的 TEMPLATE 参数。如果写的是 app_prod,被占用的是业务库;如果未指定模板、错误却指向 template1,那是默认模板库被连接占用,不能直接解释为业务库无法在线导出。
在已有管理连接中查看目标源库的会话:
SELECT pid, usename, application_name, client_addr, state
FROM pg_stat_activity
WHERE datname = 'app_prod'
ORDER BY pid;
app_prod 是示例数据库名。这个查询能帮助辨认应用、管理工具和其他任务的连接;它不终止连接。普通账号可能看不到其他会话的全部信息,应请有权限的管理员核对,不能把受限字段为空当成没有连接。
空闲连接也仍是连接。关闭一个查询窗口后,如果连接池、后台任务或管理工具立即重连,模板复制照样会失败。需要保留在线业务时,不要为了绕过此错误批量结束会话或重启整个数据库服务。
如果原本只是创建空数据库,并且不需要 template1 中添加过的对象,可以另外评估从 template0 创建;它不能替代复制业务库的数据。真正需要复制自定义模板的内容时,仍要协调连接或改用逻辑导出。
为测试副本准备明确的目标
以下例子使用这些占位值,请先替换为实际环境:源主机 db-prod.example、源库 app_prod、导出账号 dump_reader;测试主机 db-test.example、新库 app_test、测试账号 test_owner。不要将测试端连接参数复制成生产端。
导出账号必须能读取所需对象和数据,包括实际使用的序列、大对象等;有行级安全策略时,还要确认能够完整导出。遇到权限错误应补齐经过审核的授权,不应靠忽略错误生成“看似成功”的文件。测试角色需要预先存在,并具备创建目标数据库和所需对象的权限;扩展、语言及其他依赖也要提前准备。
这个示例把恢复后的普通对象交给测试账号,并跳过源库的 GRANT/REVOKE。它适合用单独账号运行测试。若要验证生产的多角色权限、行级安全策略或安全定义者函数,不能把这种所有权调整当成原样复制,应另行准备角色与权限恢复方案。
先完成导出,再创建空库和恢复
在可信的管理机器上选择一个受控目录,确保空间充足。下面是 POSIX shell 示例,umask 使随后新建的归档默认只允许当前用户访问;归档可能包含敏感数据,应按生产数据要求保管。
umask 077
pg_dump \
--host=db-prod.example --port=5432 --username=dump_reader \
--dbname=app_prod --format=custom \
--file=app_prod_snapshot.dump
使用正常的密码提示或已配置的安全认证方式,不把密码写进命令。归档名应是新文件名,避免覆盖已有备份。确认命令退出状态为 0,并检查标准错误输出后,才进入下一步;导出失败留下的文件不能作为可恢复副本。
pg_dump 能在并发访问下取得一致性导出,不要求断开正常读写连接。不过它仍会获取锁并消耗磁盘、CPU 和网络资源,可能与需要强锁的 DDL 冲突;避开结构变更窗口,并根据数据库规模安排时间。导出完成后源库继续产生的数据不会自动进入这份归档。
可以查看归档目录,确认格式能被工具识别:
pg_restore --list app_prod_snapshot.dump
这只是目录检查,不能代替一次完整恢复验证。随后在测试服务器创建尚不存在的数据库:
createdb \
--host=db-test.example --port=5432 --username=test_owner \
--template=template0 app_test
只在创建成功后恢复。这里使用空模板,避免目标库已经带有额外对象;如测试要求特定编码、区域设置或排序规则,应事先匹配源库和测试目的,不能把服务器默认值视为完全相同。
pg_restore \
--host=db-test.example --port=5432 --username=test_owner \
--dbname=app_test \
--no-owner --no-acl --no-tablespaces \
--single-transaction \
app_prod_snapshot.dump
--no-owner 不恢复源对象所有者,--no-acl 不恢复原有授权,--no-tablespaces 使用测试端默认表空间。因此这份副本有意调整了权限和存储位置,不适合据此证明生产权限或表空间配置正确。策略或函数定义中显式引用的角色等依赖也不会因此自动消失。
--single-transaction 使恢复作为一个事务执行,遇错停止;成功时整体提交,失败时不留下这次恢复已提交的一部分。它不包含前面的建库操作,也不能与并行恢复一起使用。大型数据库的长事务和锁资源开销需要单独评估,不要照抄参数后直接加并行任务。
这里不加 --create:该选项会按归档记录的原数据库名创建和恢复,而不是单纯把内容装进命令里写的测试库。也不需要 --clean,因为目标本来就是新建空库。
恢复成功后仍要用测试账号验收
命令返回成功后,通过明确的测试连接核对:
psql --host=db-test.example --port=5432 \
--username=test_owner --dbname=app_test
在这个连接中确认数据库与用户,再查看对象:
SELECT current_database(), current_user;
\dn
\dt *.*
检查关键表、序列、约束及所需扩展,并以实际测试账号完成代表性的读取和写入流程。只看到库名存在或表名列出,不代表数据、所有权和依赖都恢复成功;不要直接比较不断变化的生产库当前行数,并要求它与较早快照完全一致。
运行应用前,应隔离邮件、支付、消息队列、定时任务及其他可能连接生产的外部服务,按实际需要脱敏数据。单库导出不包含集群共享的角色和表空间定义,也不会替你改好应用连接串。
失败时保留归档,重新使用干净的测试目标
如果导出失败,停止后续步骤,保留错误信息并修复读取权限、连接或版本问题后重新导出。客户端主版本不能早于源服务器主版本;向旧版服务器恢复也没有通用兼容保证。
若恢复失败,保留归档及错误日志,核对扩展、角色引用、目标权限和版本。上述单事务恢复会回滚这次恢复的数据库改动,但提前创建的测试库仍然存在。修正条件后确认目标仍为空,或另建一个新的测试库名再试;不要在已有业务数据的库上加清理参数重跑。
整条流程无需修改或删除源库。放弃测试副本时,先停止测试应用、核实主机与库名,再按测试环境的既有清理流程处理新库和归档;不要把生产会话终止当成这项复制操作的回退步骤。


