跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

文档

了解如何使用 pgcli 管理 PostgreSQL 数据库。

使用 pgcli 管理 PostgreSQL 数据库的完整指南。

  • 快速开始 — 安装与基本用法
  • 备份 — 快照与 pgBackRest 管理
  • 恢复 — 时间点恢复(PITR)
  • 复制 — 只读副本与故障转移
  • 扩展 — 安装和管理 PostgreSQL 扩展

1 - 快速开始

安装 pgcli 并创建你的第一个 PostgreSQL 实例

几分钟内让 pgcli 运行起来。

安装

使用单条命令安装 pgcli:

curl -fsSL https://raw.githubusercontent.com/mars-base/pgcli/main/scripts/install.sh | bash

此脚本将:

  • 为你的平台下载最新的 pgcli 二进制文件
  • 安装到 /usr/local/bin(如果没有 sudo 则安装到 ~/.local/bin
  • 将 pgcli 添加到你的 PATH

初始化配置

# 初始化配置并创建默认实例
pg config init --add default --base-dir /data/pg

这会在 ~/.pgcli/pg.yaml 创建带有合理默认值的配置,包括:

  • 名为 default 的默认实例
  • 数据目录 /data/pg/default
  • 从 35432 开始自动分配的端口

启动实例

# 启动默认实例
pg start

# 查看状态和连接信息
pg status

输出会显示连接 URL、管理员密码和备份状态。

连接到数据库

# 使用 pgcli 内置的 psql 封装
pg psql

# 或使用 pg status 中显示的连接字符串直接连接
psql postgres://admin:<password>@localhost:35432/admin_db

# 直接执行 SQL
pg exec "SELECT version()"

基本操作

# 列出所有实例
pg list

# 停止实例
pg stop

# 启动实例
pg start

# 查看实例状态
pg status

# 执行 SQL
pg exec "SELECT version();"

多实例管理

# 创建额外实例
pg create -i proj01 --base-dir /data/pg
pg create -i proj02 --base-dir /data/pg

# 列出所有实例
pg list

# 启动所有实例
pg start --all

多配置文件(隔离环境)

为同一主机上的隔离测试环境或每个项目一个配置, 使用不同的 --namespace不重叠的端口范围生成独立的配置文件。

# 环境 "t1":容器前缀 pgcli-pg-t1-*,PG 端口从 38000 开始
pg config init -o ~/.pgcli-t1/pg.yaml --namespace t1 --pg-start-port 38000 --pg-ssh-port 43000 --add proj1

# 环境 "t2":不同的命名空间和端口
pg config init -o ~/.pgcli-t2/pg.yaml --namespace t2 --pg-start-port 38100 --pg-ssh-port 43100 --add proj2

# 使用 -c 管理各环境
pg -c ~/.pgcli-t1/pg.yaml start -i proj1
pg -c ~/.pgcli-t2/pg.yaml list
参数 默认值 含义
--namespace default 容器名称前缀:pgcli-pg-<namespace>-<instance>
--pg-start-port 35432 第一个 PG 主机端口;实例按顺序分配
--pg-ssh-port 42201 第一个 SSH 主机端口;按顺序分配

规划建议:

  • 始终使用显式的 --namespace 以避免容器名称冲突。
  • 同一主机上的端口范围不能重叠。
  • 命名空间在创建时固化到容器名称中。 后期更改需要 pg destroy 后重新初始化。

交互式 psql 会话

# 打开交互式 psql(默认实例)
pg psql

# 为指定实例打开 psql
pg psql -i proj01

# 从 stdin 读取 SQL(非交互,用于脚本)
echo "SELECT version();" | pg psql

# 执行单条 SQL 命令
pg psql -- -c "SHOW work_mem"

# 连接到不同数据库
pg psql -- -d postgres

# 使用 psql 元命令
pg psql -- -c "\dt"     # 列出表
pg psql -- -c "\du"     # 列出用户
pg psql -- -c "\l"      # 列出数据库

# 通过连接字符串连接远程数据库
pg psql --dsn postgres://user:pass@host:5432/db

在交互式 psql 中你可以使用:

  • 带历史记录和 tab 补全的 SQL 查询
  • psql 元命令(\dt\du\l 等)
  • \q 退出

容器 Shell

# 在容器中打开 bash(默认实例)
pg shell

# 为指定实例打开 shell
pg shell -i proj01

# 直接运行命令
pg shell -- -c "ls -la /var/lib/postgresql/data"

# 查看日志
pg shell -- -c "tail -f /var/log/postgresql/postgresql-*.log"

Shell 以 root 身份在容器内运行,可以完全访问:

  • PostgreSQL 数据目录(/var/lib/postgresql/data
  • 配置文件
  • 日志文件(/var/log/postgresql/
  • 所有系统工具和实用程序

下一步

2 - 命名空间隔离

使用命名空间在单台主机上创建隔离的环境

命名空间允许你在单台主机上创建完全隔离的环境,非常适合将生产、开发和测试环境分开,避免容器名称冲突或端口冲突。

什么是命名空间?

命名空间是应用于配置文件中所有容器名称的前缀。这种隔离机制确保:

  • 容器名称不冲突:每个命名空间有自己的容器前缀
  • 端口范围分离:每个配置文件从自己的范围分配端口
  • 备份容器隔离:每个命名空间有自己的 pgBackRest 容器
  • 配置文件独立:每个环境使用单独的配置文件

使用场景:生产和开发环境

一个常见场景是在同一台服务器上运行生产和开发环境:

# 创建生产环境
pg config init \
  --namespace prod \
  --pg-start-port 35432 \
  --pg-ssh-port 42201 \
  --add app-db \
  -o ~/.pgcli-prod/pg.yaml

# 创建开发环境
pg config init \
  --namespace dev \
  --pg-start-port 38000 \
  --pg-ssh-port 43000 \
  --add app-db \
  -o ~/.pgcli-dev/pg.yaml

这会创建两个完全隔离的环境:

环境 配置文件 容器前缀 PG 端口范围 SSH 端口范围
生产 ~/.pgcli-prod/pg.yaml pgcli-pg-prod-* 35432+ 42201+
开发 ~/.pgcli-dev/pg.yaml pgcli-pg-dev-* 38000+ 43000+

管理多个环境

使用 -c 标志指定要使用的配置文件:

# 启动生产数据库
pg -c ~/.pgcli-prod/pg.yaml start -i app-db

# 启动开发数据库
pg -c ~/.pgcli-dev/pg.yaml start -i app-db

# 列出生产环境实例
pg -c ~/.pgcli-prod/pg.yaml list

# 列出开发环境实例
pg -c ~/.pgcli-dev/pg.yaml list

工作原理

容器命名

命名空间为 prod,实例为 app-db

  • 实例容器:pgcli-pg-prod-app-db
  • 备份容器:pgcli-backup-prod
  • 网络:pgcli-net-prod(如果使用独立网络)

没有命名空间(或 --namespace ""):

  • 实例容器:pgcli-pg-default-app-db
  • 备份容器:pgcli-backup-default

端口分配

配置文件中的每个实例获得顺序端口:

  • 第一个实例:pg_start_port(例如 35432)
  • 第二个实例:pg_start_port + 1(例如 35433)
  • 依此类推…

SSH 端口同理。

配置持久化

命名空间和端口范围保存在配置文件中:

namespace: prod
pg_start_port: 35432
pg_ssh_port: 42201

最佳实践

1. 始终使用显式命名空间

在一台主机上运行多个配置时,永远不要依赖默认命名空间:

# 错误:两个配置都会使用 "default" 命名空间并冲突
pg config init --add app -o ~/.pgcli-prod/pg.yaml
pg config init --add app -o ~/.pgcli-dev/pg.yaml  # 冲突!

# 正确:显式命名空间
pg config init --namespace prod --add app -o ~/.pgcli-prod/pg.yaml
pg config init --namespace dev --add app -o ~/.pgcli-dev/pg.yaml

2. 使用不重叠的端口范围

确保配置之间的端口范围不重叠:

# 生产:35432-35999, 42201-42999
pg config init --namespace prod \
  --pg-start-port 35432 \
  --pg-ssh-port 42201 \
  --add app -o ~/.pgcli-prod/pg.yaml

# 开发:38000-38999, 43000-43999
pg config init --namespace dev \
  --pg-start-port 38000 \
  --pg-ssh-port 43000 \
  --add app -o ~/.pgcli-dev/pg.yaml

# 测试:40000-40999, 44000-44999
pg config init --namespace test \
  --pg-start-port 40000 \
  --pg-ssh-port 44000 \
  --add app -o ~/.pgcli-test/pg.yaml

为每个环境中的多个实例预留足够的端口空间。

3. 命名空间在创建时固化

命名空间在创建时嵌入容器名称。之后更改会破坏关联:

# 使用命名空间 "prod" 创建
pg -c ~/.pgcli-prod/pg.yaml start -i app-db
# 容器:pgcli-pg-prod-app-db

# 编辑配置将命名空间改为 "production"
# 这样不行 - 容器名称不匹配!

# 正确做法:销毁并重建
pg -c ~/.pgcli-prod/pg.yaml destroy -i app-db --clean-data
pg -c ~/.pgcli-prod/pg.yaml create -i app-db
pg -c ~/.pgcli-prod/pg.yaml start -i app-db

4. 使用 Shell 别名简化操作

创建别名避免重复输入 -c

# 添加到 ~/.bashrc 或 ~/.zshrc
alias pg-prod='pg -c ~/.pgcli-prod/pg.yaml'
alias pg-dev='pg -c ~/.pgcli-dev/pg.yaml'
alias pg-test='pg -c ~/.pgcli-test/pg.yaml'

# 然后使用:
pg-prod start -i app-db
pg-dev list
pg-test destroy -i test-db --force

高级用法:带副本的多环境

你甚至可以设置隔离的复制环境:

# 生产:主实例 + 副本
pg -c ~/.pgcli-prod/pg.yaml create -i primary --base-dir /data/prod
pg -c ~/.pgcli-prod/pg.yaml replica create replica -i primary

# 开发:独立的主实例 + 副本
pg -c ~/.pgcli-dev/pg.yaml create -i primary --base-dir /data/dev
pg -c ~/.pgcli-dev/pg.yaml replica create replica -i primary

每个环境维护自己的复制槽、备份 stanza 和数据目录。

故障排除

容器名称冲突

Error: container "pgcli-pg-default-app-db" already exists

原因:两个配置使用了相同的命名空间。

解决:使用不同的命名空间,或先销毁冲突的实例。

端口已被占用

Error: listen tcp 0.0.0.0:35432: bind: address already in use

原因:配置之间的端口范围重叠。

解决:使用不重叠的端口范围,并预留足够间距。

更改命名空间后找不到实例

Error: instance "app-db" exists in config but container not found

原因:创建实例后更改了配置文件中的命名空间。

解决:销毁并重建实例,或恢复命名空间更改。

总结

命名空间为单台主机上的多环境提供完全隔离:

  • 独立配置:每个环境有自己的 pg.yaml
  • 不同命名空间:防止容器名称冲突
  • 不重叠端口:避免端口冲突
  • 独立操作:每个环境使用 -c 单独管理

非常适合在同一台服务器上运行生产、开发、测试和预发布环境,互不干扰。

3 - exec 和 psql

pg exec 和 psql 指南

两种对实例运行 SQL 的方式:pg exec 用于一次性 SQL 或容器命令,pg psql 用于交互式会话。

pg exec

SQL 模式(默认)

没有 -- 的参数作为 SQL 通过 psql 执行,使用实例配置的用户和数据库。

pg exec "SELECT version()"
pg exec -i proj01 "SELECT count(*) FROM users"
pg exec "CREATE TABLE test (id serial PRIMARY KEY, msg text)"

容器命令模式(– 之后)

-- 之后的参数直接在容器内运行(以 root 身份)。

pg exec -- pg_isready
pg exec -- ls -la /var/lib/postgresql/data
pg exec -- bash -c "cat /var/lib/postgresql/data/postgresql.conf"
pg exec -- tail -f /var/log/postgresql/postgresql-*.log

远程数据库(–dsn)

通过连接字符串对任何可达数据库执行 SQL,使用临时容器。--dsn 仅支持 SQL 模式;容器命令需要本地实例。

pg exec --dsn postgres://user:pass@host:5432/db "SELECT count(*) FROM users"

pg psql

交互式会话

pg psql                          # 默认实例
pg psql -i proj01                # 特定实例

在 shell 内你获得完整的 psql 功能:带历史记录和 tab 补全的 SQL、元命令(\dt\du\l),以及 \q 退出。

非交互式(脚本)

echo "SELECT version();" | pg psql        # 来自 stdin 的 SQL
pg psql -- -c "SHOW work_mem"             # 单条命令
pg psql -- -d other_db                    # 连接到不同数据库
pg psql -- -U other_user                  # 以不同用户连接

切换到 postgres 超级用户

某些管理任务(例如创建某些扩展、修改系统级设置)需要 postgres 超级用户权限。使用 -- 传递 psql 参数并切换用户:

pg psql -i pg01 -- -U postgres -d postgres

推荐方法:日常操作使用实例默认用户(admin),仅在需要超级用户权限时切换到 postgres。这比修改配置文件或重启容器更安全更方便。

示例场景:

# 创建需要超级用户权限的扩展
pg psql -i pg01 -- -U postgres -d postgres -c "CREATE EXTENSION pg_cron"

# 配置 cron.database_name(pg_cron 特定参数)
pg psql -i pg01 -- -U postgres -d postgres -c "ALTER SYSTEM SET cron.database_name = 'pg01_db'"

# 查看系统级配置
pg psql -i pg01 -- -U postgres -d postgres -c "SHOW shared_preload_libraries"

远程数据库(–dsn)

pg psql --dsn postgres://user:pass@host:5432/db

规则

  • --dsn--instance 互斥:连接字符串确定主机、端口和数据库,因此 -i 被拒绝以避免静默误用。
  • 使用 --dsn 时,数据库是 URL 的路径部分:postgres://user:pass@host:5432/mydb 连接到 mydb。要使用另一个数据库,更改路径。
  • 使用本地实例时,-- 传递原始 psql 参数(包括 -d/-U),覆盖实例默认值。

4 - PostgreSQL 扩展

pgcli PostgreSQL 扩展指南

pgcli 支持从 Pigsty DEB 仓库安装和管理 PostgreSQL 扩展。

工作原理

扩展被烘焙到派生容器镜像中:

  1. pg extension install 构建新镜像(基于当前镜像 + Pigsty 仓库 + 扩展包)
  2. 停止并移除旧容器
  3. 从新镜像重新创建容器(主机数据卷被保留)
  4. 更新配置文件中的 image_tag

这种方法的优势:

  • 扩展在容器重建后仍然存在(烘焙到镜像层)
  • pg start 不需要在每次启动时运行 apt-get install
  • 扩展文件是持久的并与容器生命周期解耦

命令

安装扩展

# 安装单个扩展
pg extension install pg_stat_statements

# 安装多个扩展(单次镜像构建)
pg extension install pgmq uuid-ossp pg_stat_statements

# 针对特定实例
pg extension install pg_stat_statements -i pg01

重启确认: 需要 shared_preload_libraries 的扩展(例如 pg_stat_statementspg_cron)需要 PostgreSQL 重启。默认情况下,会提示你确认:

# 交互式确认(默认)
pg extension install pg_stat_statements
# 输出:
# Installing extensions that require shared_preload_libraries will cause a PostgreSQL restart.
# Extensions to be installed: [pg_stat_statements]
# This will cause a brief interruption to database connections.
# Restart PostgreSQL now? [y/N]:

# 跳过确认并自动重启
pg extension install pg_stat_statements --auto-restart

如果你拒绝重启,可以稍后应用更改:

pg stop -i pg01
pg start -i pg01

列出已安装扩展

pg extension list -i pg01

示例输出:

Installed extensions in "pg01":
  pg_stat_statements (managed)
  uuid-ossp (managed)
  plpgsql (unmanaged)
  • managed:由 pgcli 跟踪(记录在配置中,包含在镜像中)
  • unmanaged:手动安装的扩展(不在配置中跟踪)

移除扩展

pg extension remove pgmq -i pg01

工作流:

  1. DROP EXTENSION IF EXISTS pgmq
  2. 更新配置和 shared_preload_libraries
  3. 无镜像重建-ext 镜像在实例间共享,包永远不会被卸载

重启确认: 如果移除需要 shared_preload_libraries 的扩展(例如 pg_stat_statementspg_cron),会在重启前提示你确认:

# 交互式确认(默认)
pg extension remove pg_stat_statements -i pg01
# 输出:
# Removing extensions that require shared_preload_libraries will cause a PostgreSQL restart.
# Extensions to be removed: [pg_stat_statements]
# This will cause a brief interruption to database connections.
# Restart PostgreSQL now? [y/N]:

# 跳过确认并自动重启
pg extension remove pg_stat_statements -i pg01 --auto-restart

如果你拒绝重启,可以稍后应用更改:

pg stop -i pg01
pg start -i pg01

查看可用扩展

pg extension available

列出所有 440 个已知扩展:

  • 45 个内置(contrib,已在基础镜像中——无需镜像构建)
  • 395 个 Pigsty 目录(来自 Pigsty DEB 仓库,需要镜像构建)

内置扩展目录

需要 shared_preload_libraries(安装时重启)

扩展 描述
pg_stat_statements SQL 性能分析
pg_cron 定时任务执行
pg_hint_plan 查询提示
pg_stat_monitor 高级性能监控
pg_qualstats 查询谓词统计
pg_stat_kcache 内核级性能统计
pg_wait_sampling 等待事件采样
pg_track_settings 配置更改跟踪
timescaledb 时序数据库扩展

不需要 shared_preload_libraries(无需重启)

扩展 描述
uuid-ossp UUID 生成函数
pgmq 轻量级消息队列
hstore 键值对存储
pgcrypto 加密函数
tablefunc 交叉表函数
btree_gist B-tree GiST 索引支持
btree_gin B-tree GIN 索引支持
pg_trgm 三元组相似性匹配
unaccent 去重音函数
fuzzystrmatch 模糊字符串匹配
intarray 整数数组操作
isn ISBN/ISSN/EAN 标准数字类型
pg_repack 在线表重组
pg_squeeze 表空间回收
pg_partman 分区管理
pgvector 向量相似性搜索
postgis 地理空间数据支持

目录外的扩展

只有目录中的扩展(builtin + Pigsty)可以通过 pg extension install 安装。未知扩展名称会在构建开始前被拒绝:

  [X] Unknown extension(s): [nonexistent_ext]

      These extensions are not in the Pigsty catalog or builtin contrib list.
      Check available extensions: pg extension available
      Full Pigsty catalog: https://pigsty.cc/ext/list/

完整目录:https://pigsty.cc/ext/list/

配置

安装扩展后,配置会被更新:

instances:
  pg01:
    extensions:
      - pg_stat_statements
      - uuid-ossp
      - pgmq
    podman:
      image_tag: ghcr.io/mars-base/pgcli/pgcli-pg:18-2.58.0-ext

image_tag 指向包含所有已安装扩展的派生镜像。

共享预加载库

需要 shared_preload_libraries 的扩展会在 postgresql.conf 中自动配置:

# === pgcli extensions (managed — do not edit) ===
shared_preload_libraries = 'pg_stat_statements,pg_cron'
# === end pgcli extensions ===

这是 postmaster 级别参数;更改后必须重启 PostgreSQL。

故障排除

扩展安装失败

  [X] Unknown extension(s): [nonexistent_ext]

原因:扩展名称不在内置 contrib 列表或 Pigsty 目录中。

解决方法:

  • 验证扩展名称:pg extension available
  • 检查 Pigsty 目录:https://pigsty.cc/ext/list/
  • 注意确切的 SQL 扩展名称(例如 vector 而不是 pgvector

CREATE EXTENSION 失败

ERROR: extension "pgmq" already exists

扩展已安装但未在配置中跟踪。你可以安全地忽略这个,或手动将其添加到配置:

extensions:
  - pgmq

共享预加载库冲突

如果 shared_preload_librariespostgresql.conf 中被手动编辑,pgcli 的标记块会覆盖它。

解决方法:删除手动配置并让 pgcli 管理它。

注释

  • 扩展数量:45 个内置(contrib)+ 395 个 Pigsty 目录 = 440 个已知扩展
  • 镜像大小:每个扩展增加 10-50MB 到镜像,但 Pigsty 包已优化
  • 构建时间:首次扩展安装需要 1-3 分钟(下载 + 构建);后续安装更快(缓存命中)
  • 副本行为:副本可以安装扩展,但 CREATE EXTENSION 会被拒绝(只读)。在主实例上安装;副本通过物理复制同步
  • 扩展升级ALTER EXTENSION ... UPDATE TO ... 尚不支持;通过 pg exec 手动运行

5 - 备份

pgcli 备份指南

快照

# 创建快照(完整备份)
pg snapshot create -i proj01

# 创建差异备份(推荐)
pg snapshot create --type diff -i proj01

# 快照期间流式输出备份容器日志
pg snapshot create --tail-logs -i proj01

# 列出快照
pg snapshot list -i proj01

# 限制显示的快照数量
pg snapshot list --limit 5 -i proj01

# 删除快照
pg snapshot delete 20260826-073712F -i proj01

快照类型:

  • full — 完整备份(默认,自包含)
  • diff — 自上次完整备份以来的更改
  • incr — 自上次备份以来的更改

共享备份容器

所有实例共享单个 pgbackrest 容器;每个实例在存储库中都有自己的 stanza。

# 初始化共享 pgbackrest 容器(构建镜像、创建目录、生成配置)
pg backup setup

# 使用自定义基础目录存储备份数据和日志
pg backup setup --base-dir /mnt/backup

# 启动 / 停止备份容器
pg backup start
pg backup stop

# 显示备份容器状态
pg backup status

备份基础设施(网络、镜像、目录、配置、容器)在 pg start 时自动准备;手动运行 pg backup setup 重新初始化,例如更改基础目录之后。

6 - 副本(只读备用)

pgcli 副本(只读备用)指南

创建现有实例的只读物理副本。副本通过 PostgreSQL 物理复制从其主实例持续流式传输 WAL,并提供只读查询服务——适用于读写分离、报告或作为热备。

# 创建默认实例的副本
pg replica create ro1

# 创建特定实例的副本
pg replica create ro1 -i proj01

# 列出副本和复制延迟
pg replica list

发生了什么

  1. 预检 — 主实例必须正在运行(在任何配置写入之前验证;停止的主实例会失败但没有副作用)
  2. 注册 — 添加新实例条目,使用主实例的数据库名称和密码(参见注释),禁用 PITR,replica_of 设置为主实例
  3. 复制设置 — 在主实例上:
    • pg_hba.conf 为回环地址和 RFC1918 范围添加 host replication 条目(幂等)
    • 创建物理复制槽 pgcli_r_<name>,保留 WAL 以确保副本不会落后于 WAL 回收
  4. 基础备份pg_basebackup -R 将主实例的数据目录复制到副本的数据目录,写入 primary_conninfo(带密码)和 standby.signal,使副本以备用模式启动
  5. 启动 — 副本容器启动并持续流式传输 WAL

验证

# 只读备用?
pg exec -i ro1 "SELECT pg_is_in_recovery()"      # t

# 写入被拒绝
pg exec -i ro1 "INSERT INTO t VALUES (1)"         # 只读事务错误

# 流式传输活跃
pg exec -i ro1 "SELECT pg_is_in_recovery(), now() - pg_last_xact_replay_timestamp()"

# 主实例上的槽是活跃的
pg exec -i primary "SELECT slot_name, active FROM pg_replication_slots"

# 概览
pg list                              # ROLE/PRIMARY 列
pg replica list                      # NAME/PRIMARY/STATUS/LAG
pg status -i ro1                     # Role: standby (replica of ...)

销毁

销毁副本是两步操作:

# 步骤 1:销毁副本实例(移除容器 + 数据 + 配置条目)
pg destroy -i ro1 --clean-data --force

# 步骤 2:在主实例上删除复制槽(如果已不存在则为无操作)
pg replica drop ro1 -i <primary>

步骤 1 必须在步骤 2 之前运行:PostgreSQL 拒绝删除仍在流式传输的槽(replication slot is active),因此必须先销毁副本以关闭其流式连接。

注意: 如果主实例是本地 pgcli 管理的实例(同一主机),可以跳过步骤 2 — destroy 会自动清理槽。当从副本主机无法访问主实例时需要步骤 2。

跨网络副本

上述同主机流程假设主实例和副本共享一个服务器(一个 podman 守护进程,一个网络)。对于在另一台主机上的副本,pgcli 在每一侧运行一个命令——无需 SSH,唯一的跨机器信息就是你作为参数传递的内容:

# ---- 在主实例主机上:准备主实例(首先运行)----
pg replica create ro1 -i pg01 --replica-host 10.241.20.100

# ---- 在副本主机上:复制数据并启动副本(其次运行)----
pg replica create ro1 --primary-dsn "postgres://admin:<password>@10.241.20.50:35432/pg01_db" --primary-name pg01

获取主实例 DSN

如果主实例是 pgcli 管理的实例,使用 pg status 获取其连接信息:

pg status -i pg01
# ...
#   Connection: postgres://admin:fbcQx9uIzvTO6dVJ@127.0.0.1:35432/pg01_db

然后将 127.0.0.1 替换为从副本主机看到的主实例主机 IP(例如 10.241.20.50)——用户、密码和数据库按原样使用。注意主实例主机必须接受从副本主机到该端口的 TCP 连接(防火墙/安全组)。

每一侧的作用:

  • 主实例侧--replica-host <ip|hostname>):仅准备主实例——本地不创建任何内容。它将 host replication all <addr> 条目追加到 pg_hba.conf 并创建物理槽 pgcli_r_<name>,然后打印要在副本主机上运行的确切 --primary-dsn 命令。IP 地址获得 /32(IPv6 为 /128)掩码;主机名按原样写入。已在托管 RFC1918 范围(10.0.0.0/8172.16.0.0/12192.168.0.0/16)内的 IP 会被跳过去重。幂等——重新运行不会添加重复行。
  • 副本侧--primary-dsn):首先验证主实例上存在槽(验证连接性和顺序——在主实例侧之前运行会失败并带有可操作的错误消息且没有副作用),然后注册实例,通过网络从 DSN 运行 pg_basebackup(主机网络),并启动备用实例。

副本侧仅在副本主机上运行:副本实例的用户、数据库和密码来自 DSN(物理复制复制 pg_authid,因此本地密码必须等于主实例的密码)。主实例名称使用 --primary-name 给出并记录为 replica_of;它不必存在于副本主机的配置中,-i 不用于远程主实例——如果给出,它保持其严格含义并且必须引用真实的本地实例。

销毁是对称的,每个主机一个命令——按此顺序

# 1. 在副本主机上:移除容器 + 配置,还通过 DSN 删除远程主实例上的槽
#    (如果主实例可达则自动)
pg destroy -i ro1

# 2. 在主实例主机上(仅当步骤 1 的 DSN 连接失败时):
#    手动删除槽
pg replica drop ro1 -i pg01

步骤 1 必须在步骤 2 之前运行:PostgreSQL 拒绝删除仍在流式传输的槽(replication slot is active),因此必须先销毁副本以关闭其流式连接。

自动槽清理: 当跨主机副本设置了 PrimaryDSN 时,destroy 会自动尝试通过 DSN 删除远程主实例上的复制槽。如果主实例可达,则无需手动步骤 2。replica drop 是幂等的——当槽已不存在时重新运行会作为无操作成功。

非 pgcli 主实例

主实例不必由 pgcli 管理——只要主实例侧已手动准备(槽检查仅验证槽是否存在,不验证谁创建了它),副本侧可以对任何 PostgreSQL 服务器工作:

  1. pg_hba.conf 中允许从副本主机复制,然后重新加载(SELECT pg_reload_conf()):
    host replication <replica user> <replica ip>/32 scram-sha-256
  2. 使用确切名称 pgcli_r_<replica-name> 创建物理槽(副本侧检查此名称):
    SELECT pg_create_physical_replication_slot('pgcli_r_ro1');
    需要 wal_level = replica(或 logical)和具有 REPLICATION 权限的用户——DSN 用户。

然后副本侧命令不变:

pg replica create ro1 --primary-dsn "postgres://<user>:<pass>@<primary ip>:5432/<db>" --primary-name pg01

销毁时,主实例侧没有 pgcli——在 pg destroy -i ro1 之后,手动删除槽:

SELECT pg_drop_replication_slot('pgcli_r_ro1');

如果基础备份失败(例如网络中断),销毁副本并重新运行副本侧命令——主实例侧的槽和 hba 条目保持有效。

注释

  • 只读 — 副本拒绝所有写入(cannot execute INSERT in a read-only transaction)。要使其可写你需要提升它(pg_ctl promote),这尚未作为 pgcli 命令暴露
  • 相同数据,相同密码 — 物理复制是主实例的逐字节复制,包括 pg_authid。因此副本的管理员密码和数据库名称与主实例相同;只有容器名称、端口和数据目录不同。使用 --dsn 风格的连接时使用副本的端口
  • 副本上禁用 PITR — 备用实例不归档任何内容,也不在 pgBackRest 备份容器中注册;备份在主实例上运行
  • 主实例必须正在运行 — 初始创建(pg_basebackup)和持续流式传输都需要;如果主实例重启,副本会自动重新连接(槽显示为 active
  • 延迟显示replica list 延迟(now() - pg_last_xact_replay_timestamp())在主实例空闲时会增长;它会在下一个复制的事务时降回零。这是预期的空闲行为,不是漂移
  • 幂等启动 — 重复的 pg start -i ro1 在数据目录已初始化时跳过基础备份

7 - 故障转移:副本提升

pgcli 故障转移:副本提升指南

当当前主实例失败时,将副本提升为新的主实例。pgcli 提供 3 步手动故障转移工作流——每个步骤在其各自的主机上运行,不自动检测同主机与跨主机拓扑。

概述

                    ┌─────────────┐
                    │   primary   │  ← 崩溃 / 变得不可用
                    │  (pg01)     │
                    └──────┬──────┘
                           │ WAL 流式传输
               ┌───────────┼───────────┐
               ▼           ▼           ▼
          ┌─────────┐ ┌─────────┐ ┌─────────┐
          │  ro1    │ │  ro2    │ │  ro3    │
          │ replica │ │ replica │ │ replica │
          └─────────┘ └─────────┘ └─────────┘

故障转移后(提升 ro1 → 新主实例):

                    ┌─────────────┐
                    │   ro1       │  ← 新主实例(已提升)
                    │  (primary)  │
                    └──────┬──────┘
                           │ WAL 流式传输
               ┌───────────┼───────────┐
               ▼           ▼           ▼
          ┌─────────┐ ┌─────────┐ ┌─────────┐
          │  ro2    │ │  ro3    │ │  pg01   │
          │ replica │ │ replica │ │ replica │  ← 降级的旧主实例
          └─────────┘ └─────────┘ └─────────┘

3 步故障转移

每个步骤都是独立的命令。按顺序在各自的主机上运行它们。

# 步骤 1:在被提升的副本上
pg replica promote ro1

# 步骤 2:在旧主实例主机上(当它恢复时)
pg replica drop ro1 -i pg01

# 步骤 3:在每个剩余副本主机上
pg replica repoint ro2 --primary-dsn "postgres://admin:<pw>@<new-primary-ip>:<port>/<db>" --primary-name ro1
pg replica repoint ro3 --primary-dsn "postgres://admin:<pw>@<new-primary-ip>:<port>/<db>" --primary-name ro1

步骤 1:pg replica promote <name>

在被提升为主实例的副本主机上运行。

pg replica promote ro1

发生了什么:

  1. 验证实例是副本(ReplicaOf 已设置)且容器正在运行
  2. 调用 pg_promote()(PostgreSQL 12+ 原生提升——无需容器重启)
  3. 等待恢复结束(通常亚秒级)
  4. 通过 ALTER SYSTEM RESETpostgresql.auto.conf 清理 primary_conninfo
  5. 更新配置:清除 ReplicaOfPrimaryDSN,启用 PITR
  6. 自动初始化 PITR:
    • pgBackRest stanza 创建
    • archive_mode / archive_command 配置
    • PostgreSQL 重启以应用 postmaster 级别参数
  7. 打印下一步指令

幂等: 如果副本已被提升(例如通过手动 pg_ctl promote),命令会跳到配置更新。

步骤 2:pg replica drop <name> -i <old-primary>

旧主实例主机上运行以清理复制槽。此步骤仅在特定场景中需要。

pg replica drop ro1 -i pg01

这会在旧主实例上删除物理复制槽 pgcli_r_ro1。没有清理,槽会无限期保留 WAL,直到主实例磁盘空间耗尽。

何时运行:

场景 操作 原因
旧主实例永久丢失 跳过 槽随服务器一起消失
计划将旧主实例降级为副本 跳过 repoint 销毁数据目录(包括 pg_replslot/),所有槽被隐式移除
旧主实例已恢复,继续作为独立主实例运行 必须运行 槽无限期保留 WAL;没有清理磁盘最终会填满
旧主实例已恢复但将被关闭 可选 如果实例不再运行,跳过无害

保持旧主实例不变? 如果你想保留旧主实例及其原始数据(例如用于取证分析或作为只读归档),你可以简单地不理会它——不要在其上运行 droprepoint。旧主实例继续作为具有陈旧数据的独立实例运行。只是要注意被提升副本的复制槽仍然存在并会累积 WAL;你可能想要仅删除那个特定的槽(pg replica drop ro1 -i pg01)同时保持其他一切不变。

步骤 3:pg replica repoint <name> --primary-dsn <dsn> --primary-name <name>

每个剩余副本主机上运行以将其重新指向新主实例。

pg replica repoint ro2 \
  --primary-dsn "postgres://admin:fbcQx9uIzvTO6dVJ@10.241.21.97:35439/pg01_db" \
  --primary-name ro1

发生了什么:

  1. 通过 DSN 查询新主实例的扩展(pg_extension 目录)
  2. 如果存在非内置扩展(例如 pg_cron、timescaledb),构建本地 -ext 镜像并匹配包
  3. 停止旧副本容器并销毁其数据目录
  4. 通过 DSN 在新主实例上创建复制槽
  5. 更新配置:ReplicaOfPrimaryDSNImageTagExtensions,禁用 PITR
  6. 通过 pg_basebackup -R 从新主实例重新初始化
  7. 以备用模式启动副本容器

为什么销毁 + 重建而不是 ALTER SYSTEM SET?

提升后,新主实例进入新时间线。旧时间线上的其他副本不能简单地更改 primary_conninfo——PostgreSQL 会拒绝连接:

FATAL: requested starting point on timeline 1 is not in this server's history

唯一安全的方法是从新主实例进行完整的 pg_basebackup

获取主实例 DSN

从被提升副本主机上的 pg status 获取新主实例的连接字符串:

pg status -i ro1
# Connection: postgres://admin:fbcQx9uIzvTO6dVJ@127.0.0.1:35439/pg01_db

127.0.0.1 替换为从副本主机可达的新主实例主机 IP(例如 10.241.21.97)。

降级旧主实例

当旧主实例恢复时,你可以使用相同的 repoint 命令将其作为新主实例的副本重新加入:

# 在旧主实例主机上
pg replica repoint pg01 \
  --primary-dsn "postgres://admin:fbcQx9uIzvTO6dVJ@10.241.21.97:35439/pg01_db" \
  --primary-name ro1

即使 pg01 曾是主实例(未设置 ReplicaOf)这也有效。该命令:

  1. 停止 pg01 并销毁其数据(包括旧的 PITR stanza)
  2. 在新主实例上为 pg01 创建复制槽
  3. 设置 ReplicaOf = "ro1"PITR.Enabled = false
  4. 通过 pg_basebackup 从新主实例重新初始化

重新指向后,pg01 作为只读副本从新主实例流式传输 WAL——没有 WAL 归档,没有备份。

扩展同步

当副本被重新指向新主实例时,pgcli 会自动同步扩展:

  1. 查询 — 通过 DSN 连接到新主实例并查询 pg_extension 获取已安装的扩展
  2. 过滤 — 识别非内置扩展(需要外部包的扩展,例如 pg_cron、pgmq、timescaledb)
  3. 构建 — 如果存在非内置扩展,构建本地 -ext 镜像:
    • 如果本地 -ext 镜像已存在,在其上安装缺失的包(重用 Pigsty 仓库——快速)
    • 如果没有 -ext 镜像,从基础镜像构建并设置 Pigsty 仓库
    • apt-get install 是幂等的——安装已存在的包是无操作
  4. 应用 — 副本启动时,ApplyExtensionsshared_preload_libraries 写入 postgresql.conf
  5. 跳过 CREATE EXTENSION — 副本是只读的;扩展通过 pg_basebackup + WAL 流式传输从主实例复制

这确保副本容器具有 postgresql.auto.conf 中引用的所需共享库(例如 pg_cron)。

同主机 vs 跨主机

pgcli 自动检测拓扑。你选择在每个命令运行的位置:

场景 步骤 1 步骤 2 步骤 3
全部在同一主机 pg replica promote ro1 pg replica drop ro1 -i pg01 pg replica repoint ro2 --primary-dsn "postgres://...@127.0.0.1:..." --primary-name ro1
主实例 + 副本分散在多个主机 在副本主机 在旧主实例主机 在每个副本主机上使用新主实例的网络 IP
混合 在各自的主机 在旧主实例主机 在每个副本主机

--primary-dsn 必须使用从 repoint 运行所在主机可达的 IP/主机名。

完整示例

# ── 初始设置:pg01(主实例)+ ro1, ro2(副本)在同一主机 ──

$ pg list
NAME    ROLE      PRIMARY   STATUS
pg01    primary   -         Up 2 hours
ro1     replica   pg01      Up 1 hour
ro2     replica   pg01      Up 1 hour

# ── pg01 崩溃 ──

# 步骤 1:提升 ro1
$ pg replica promote ro1
  [OK] pg_promote() signaled
  [OK] recovery ended, instance is now read-write
  [OK] primary_conninfo removed from postgresql.auto.conf
✓ Replica "ro1" promoted to primary

$ pg start -i ro1           # 启用 PITR + WAL 归档

# 步骤 2:在旧主实例上清理(如果 pg01 永久丢失则跳过)
$ pg replica drop ro1 -i pg01
  [OK] replication slot "pgcli_r_ro1" removed from primary "pg01"

# 步骤 3:将 ro2 重新指向新主实例
$ pg replica repoint ro2 \
    --primary-dsn "postgres://admin:fbcQx9uIzvTO6dVJ@127.0.0.1:35437/pg01_db" \
    --primary-name ro1
  [OK] extension image built with pg_cron, pgmq, timescaledb
  [OK] replication slot "pgcli_r_ro2" created on new primary
  [OK] config updated (ReplicaOf = "ro1")
✓ Replica "ro2" re-pointed to "ro1"

# ── pg01 恢复,降级为副本 ──
$ pg replica repoint pg01 \
    --primary-dsn "postgres://admin:fbcQx9uIzvTO6dVJ@127.0.0.1:35437/pg01_db" \
    --primary-name ro1
  [OK] backup stanza removed: pgcli_pg01
  [OK] replication slot "pgcli_r_pg01" created on new primary
  [OK] config updated (ReplicaOf = "ro1", PITR disabled)
✓ Replica "pg01" re-pointed to "ro1"

# ── 最终状态 ──
$ pg list
NAME    ROLE      PRIMARY   STATUS
pg01    replica   ro1       Up 30 seconds    # 已降级
ro1     primary   -         Up 10 minutes    # 新主实例
ro2     replica   ro1       Up 5 minutes

跨主机示例

# ── 设置:ra3(主实例,主机 A)+ ra2(副本,主机 A)+ ro2(副本,主机 B)──

# ra3 崩溃。在主机 A 上,提升 ra2:
$ pg replica promote ra2
  [OK] pg_promote() signaled
  [OK] recovery ended
  [OK] PITR initialized (stanza + archive_mode)
✓ Replica "ra2" promoted to primary

# 在主机 B(10.241.20.147)上,将 ro2 重新指向新主实例 ra2(主机 A = 10.241.21.97):
$ pg replica repoint ro2 \
    --primary-dsn "postgres://admin:fbcQx9uIzvTO6dVJ@10.241.21.97:35438/pg01_db" \
    --primary-name ra2
-> New primary has 3 non-builtin extension(s): pg_cron, pgmq, timescaledb
-> Extension image already has all required packages
  [OK] replication slot "pgcli_r_ro2" created on new primary
  [OK] config updated (ReplicaOf = "ra2", image = ...-ext)
✓ Replica "ro2" re-pointed to "ra2"

# 验证跨主机复制:
$ pg exec -i ra2 "INSERT INTO test(msg) VALUES ('after failover')"
$ pg exec -i ro2 "SELECT * FROM test ORDER BY id DESC LIMIT 1"
   msg: after failover

级联复制

副本本身可以作为下游副本的主实例,形成级联链。这减少主实例的负载并启用分层拓扑。

primary (ra3)
  ├─→ replica (ra2) ← ra2_ro1 的上游
  │     └─→ replica (ra2_ro1)
  └─→ replica (pg01)

工作原理

  1. 创建副本的副本:使用副本作为 -i 目标

    # ra2 是 ra3 的副本,创建 ra2_ro1 作为 ra2 的副本
    pg replica create ra2_ro1 -i ra2
  2. WAL 传播

    • ra2 从 ra3 流式传输 WAL
    • ra2_ro1 从 ra2 流式传输 WAL
    • 数据流:ra3 → ra2 → ra2_ro1
  3. 复制槽:每个链接维护自己的槽

    • ra3 有槽 pgcli_r_ra2
    • ra2 有槽 pgcli_r_ra2_ro1

优势

  • 减少主实例负载:只有直接副本连接到主实例
  • 地理分布:主实例 → 区域副本 → 本地副本
  • 网络效率:本地副本可以共享区域上游

限制

  • 增加延迟:每一跳增加复制延迟
  • 级联故障:如果 ra2 失败,ra2_ro1 失去其上游
  • 提升复杂性:提升 ra2_ro1 需要将其重新指向新主实例

验证级联

# 检查 ra2 的下游副本
pg exec -i ra2 "SELECT client_addr, state FROM pg_stat_replication"

# 检查 ra2_ro1 的上游
pg exec -i ra2_ro1 "SELECT conninfo FROM pg_stat_wal_receiver"

级联故障转移

如果 ra2(中间节点)失败:

# 选项 1:将 ra2_ro1 直接重新指向 ra3
pg replica repoint ra2_ro1 \
  --primary-dsn "postgres://admin:password@ra3-host:5432/pg01_db" \
  --primary-name ra3

# 选项 2:等待 ra2 恢复(一旦 ra2 重新连接到 ra3 自动进行)

如果 ra3(主实例)失败且 ra2 被提升:

# 步骤 1:提升 ra2
pg replica promote ra2

# 步骤 2:ra2_ro1 自动跟随(它已经从 ra2 复制)
# ra2_ro1 无需操作

# 步骤 3:将其他副本重新指向新主实例
pg replica repoint pg01 \
  --primary-dsn "postgres://admin:password@ra2-host:5432/pg01_db" \
  --primary-name ra2

注释

  • pg_promote() — PostgreSQL 12+ 原生函数,无需容器重启。实例就地退出恢复并立即变为可读写
  • 时间线分歧 — 提升后,新主实例在新时间线上。其他副本不能用 ALTER SYSTEM SET primary_conninfo 重新指向——它们必须通过 pg_basebackup 重建
  • 被提升副本上的 PITR — 提升后,运行 pg start 创建 pgBackRest stanza 并启用 WAL 归档。被提升的副本没有先前的备份历史
  • 复制槽 — 旧主实例为被提升副本的槽在提升后变得陈旧。pg replica drop 清理它。如果旧主实例被降级为副本,repoint 销毁旧数据且陈旧的槽不再被引用
  • 扩展 — 副本容器通过 postgresql.auto.conf 从主实例继承 shared_preload_libraries。repoint 命令确保本地镜像在重建副本之前具有所需的扩展包
  • 跳过 CREATE EXTENSION — 副本是只读的;pg_basebackup 从主实例复制扩展元数据,因此不需要 CREATE EXTENSION(且会失败 “cannot execute CREATE EXTENSION in a read-only transaction”)

8 - 管理

pgcli 管理指南

Shell 补全

为命令、标志和实例名称启用 tab 补全。

Bash

# Linux
pg completion bash > /etc/bash_completion.d/pg

# macOS(使用 Homebrew bash-completion)
pg completion bash > $(brew --prefix)/etc/bash_completion.d/pg

# 或在当前会话中加载
source <(pg completion bash)

Zsh

# 启用补全系统(一次)
echo "autoload -U compinit; compinit" >> ~/.zshrc

# 安装补全
pg completion zsh > "${fpath[1]}/_pg"

Fish

pg completion fish > ~/.config/fish/completions/pg.fish

PowerShell

pg completion powershell > pg.ps1
# 从你的 PowerShell 配置文件加载

PostgreSQL 配置

通过 pg exec 使用 ALTER SYSTEM 修改 PostgreSQL 运行时参数,然后重新加载:

# 更改参数
pg exec "ALTER SYSTEM SET work_mem = '256MB'"
pg exec "SELECT pg_reload_conf()"

# 针对特定实例
pg exec -i proj01 "ALTER SYSTEM SET effective_cache_size = '4GB'"
pg exec -i proj01 "SELECT pg_reload_conf()"

注意: 某些参数(例如 shared_buffersmax_connections)需要重启而不是重新加载。使用 pg stop && pg start 应用这些更改。

配置文件管理

检查或验证配置文件(默认为 ~/.pgcli/pg.yaml,使用 -c 覆盖)。

# 显示当前配置(YAML)
pg config show

# 以 JSON 格式显示配置
pg config show --json

# 验证配置文件结构
pg config validate

Init 生成默认配置;--add 在同一文件中创建命名实例,-o 写入自定义路径:

pg config init --add default --base-dir /data/pg
pg config init --add proj01 --base-dir /data/pg -o ./my-pg.yaml

在一个主机上运行多个配置的隔离参数(参见快速开始进行规划):

pg config init --namespace t1 --pg-start-port 38000 --pg-ssh-port 43000 --add proj01 -o ~/.pgcli-t1/pg.yaml
参数 默认值 含义
--namespace default 容器名称前缀:pgcli-pg-<namespace>-<instance>,备份容器 pgcli-backup-<namespace>。传递 --namespace "" 保持旧式名称不带前缀
--pg-start-port 35432 分配范围中的第一个 PG 主机端口
--pg-ssh-port 42201 分配范围中的第一个 SSH 主机端口

所有三个都保存到配置文件中(namespacepg_start_portpg_ssh_port);跨配置使用不重叠的端口范围,这样分配永远不会冲突。

9 - 克隆

pgcli 克隆指南

创建新实例,其数据从现有实例复制,直接流式传输——磁盘上无临时文件。

# 克隆默认实例
pg clone test02

# 克隆特定实例
pg clone test02 -i proj01

# 通过连接字符串克隆远程数据库
pg clone test02 --dsn postgres://user:pass@host:5432/db

# 新实例的自定义数据目录
pg clone test02 -i proj01 --base-dir /data/pg

发生了什么

  1. 预检 — 在创建任何内容之前验证源:
    • 本地实例:容器必须正在运行
    • --dsn:认证的 SELECT 1 必须成功(捕获错误密码、不可达主机)
  2. 创建 — 新实例条目添加到配置,带有随机密码、自己的容器名称、数据目录和自动分配的端口
  3. 启动 — 新实例启动(与 pg start 相同的工作流)
  4. 流式传输 — 源数据管道传输到目标,每秒显示一次实时传输进度

注释

  • 源实例必须正在运行(或 --dsn 目标可达);错误的源会立即失败且无副作用
  • 新实例名称在配置中不能已存在
  • --dsn--instance 互斥:使用 --dsn 时连接字符串确定主机、端口和数据库
  • 新实例获得新的随机密码——在克隆输出或 pg status -i <name> 中查找
  • 仅逻辑复制(模式 + 数据);对于大型数据库,物理方法可能更快

10 - 数据导入/导出

pgcli 数据导入/导出指南

导出和导入数据库到转储文件。支持自定义格式(推荐)和纯 SQL,带有自动 gzip 压缩。还支持在实例间管道传输。

# 导出为自定义格式(推荐,最快恢复)
pg export -i proj01 -o backup.dump

# 导出为 SQL 格式(人类可读)
pg export -i proj01 -o backup.sql

# 导出时使用 gzip 压缩(从 .gz 扩展名自动检测)
pg export -i proj01 -o backup.dump.gz
pg export -i proj01 -o backup.sql.gz

# 导出特定数据库
pg export -i proj01 -d mydb -o backup.dump

# 导出时指定压缩级别(0-9)
pg export -i proj01 -o backup.sql.gz --compress=9

# 导出时显示详细输出(显示进度)
pg export -i proj01 -o backup.dump -v

# 从自定义格式导入
pg import -i proj02 backup.dump

# 从 SQL 格式导入
pg import -i proj02 backup.sql

# 导入压缩文件(从 .gz 扩展名自动检测)
pg import -i proj02 backup.dump.gz

# 导入到特定数据库
pg import -i proj02 -d mydb backup.dump

# 导入时清理(恢复前删除现有对象)
pg import -i proj02 --clean backup.dump

# 导入时显示详细输出
pg import -i proj02 backup.dump -v

# 在实例间管道传输(无临时文件)
pg export -i proj01 | pg import -i proj02
pg export -i proj01 -d mydb | pg import -i proj02 -d mydb --clean

# 通过 SSH 跨主机管道传输
pg export -i proj01 | ssh user@remote "pg import -i proj02"
ssh user@remote "pg export -i proj01" | pg import -i proj02
ssh user@host1 "pg export -i proj01" | ssh user@host2 "pg import -i proj02"

# 通过连接字符串使用远程数据库(--dsn)
pg export --dsn postgres://user:pass@host:5432/mydb -o backup.dump
pg import --dsn postgres://user:pass@host:5432/mydb backup.dump --clean
pg export -i proj01 | pg import --dsn postgres://user:pass@host:5432/mydb
pg export --dsn postgres://user:pass@host1:5432/db1 | pg import --dsn postgres://user:pass@host2:5432/db2

# DSN 也可用于本地实例(当端口与默认值不同时很有用)
pg export --dsn postgres://admin:pass@127.0.0.1:35432/mydb | pg import --dsn postgres://admin:pass@127.0.0.1:35433/mydb --clean

格式比较:

特性 自定义(.dump SQL(.sql
导入速度 更快(二进制格式) 更慢(文本格式)
文件大小 更小(压缩) 更大(纯文本)
人类可读
选择性恢复 是(特定表)
最适合 迁移、备份、大型数据库 版本控制、CI 种子数据、手动编辑

格式检测: 使用魔数(基于内容)配合扩展名回退。

  • PGDMP 开头的文件 → 自定义格式
  • .sql.sql.gz → 纯 SQL 格式
  • .gz 扩展名或 gzip 魔数(0x1f 0x8b) → 自动解压
  • 如果内容检测失败则使用扩展名作为回退

远程数据库(–dsn): 使用连接字符串连接到任何 PostgreSQL 实例。

  • 无需本地 PostgreSQL 安装
  • 适用于本地到远程、远程到本地和远程到远程迁移
  • 支持与本地实例相同的所有标志(-o-d--clean-v--compress

关于现有数据的说明: 导入到包含现有表的数据库会失败,除非你使用 --clean 标志,它会在恢复前删除对象。导入到已包含数据的数据库时使用 --clean

用例:

  • 在实例间迁移数据:pg export -i proj01 | pg import -i proj02
  • 跨主机迁移:pg export -i proj01 | pg import --dsn postgres://user:pass@remote:5432/db
  • 与团队共享数据库:pg export -i proj01 -o dump.dump.gz(压缩,更小文件)
  • 重大更改前备份:pg export -i proj01 -o pre-migration.sql.gz
  • CI/CD 管道:导出测试数据,导入到新的测试数据库

11 - 恢复

pgcli 时间点恢复(PITR)指南

时间点恢复(PITR)

恢复到首次备份后的任意时间点。

# 恢复(只读,提交前检查)
pg restore --time "2026-08-26 15:30:00+00"

# 预览将要恢复的内容而不执行(试运行)
pg restore --time "2026-08-26 15:30:00+00" --dry-run

# 恢复期间流式输出恢复容器日志
pg restore --time "2026-08-26 15:30:00+00" --tail-logs

# 如果需要可以尝试不同的时间
pg restore --time "2026-08-26 15:25:00+00"

# 提升为读写(切换时间线)
pg restore --time "2026-08-26 15:30:00+00" --promote

# 跳过确认
pg restore --time "2026-08-26 15:30:00+00" --promote --force

时间格式:

  • 2026-08-26 15:30:00+08:00 — 带时区偏移
  • 2026-08-26 15:30:00+08 — 仅时区小时
  • 2026-08-26 15:30:00Z — UTC
  • 2026-08-26 15:30:00 — 假定为 UTC

恢复工作流: 停止 → 恢复 → 启动 → WAL 重放到目标时间

注意: --promote 之后,在进行进一步的 PITR 之前需要创建新的完整快照。

12 - 销毁

销毁实例并移除其配置

destroy 会停止并移除容器,然后从配置文件中删除该实例。默认情况下,主机的数据目录会被保留。使用 --clean-data 可以同时移除数据、WAL 归档和 pgBackRest 仓库的 stanza。

基本用法

# 销毁实例(保留数据目录)
pg destroy -i proj01

# 无需确认直接销毁
pg destroy -i proj01 --force

# 销毁并清理数据(全新开始)
pg destroy -i proj01 --clean-data

# 跳过确认并清理所有数据
pg destroy -i proj01 --clean-data --force

执行过程

运行 pg destroy 时:

  1. 停止容器 — PostgreSQL 优雅关闭
  2. 移除容器 — 删除 Podman 容器
  3. 移除配置 — 从 ~/.pgcli/pg.yaml 中删除该实例条目
  4. 保留数据(默认)— 主机数据目录 --base-dir/<instance> 保留

使用 --clean-data 时:

  1. 以上所有步骤,加上:
  2. 移除主机数据 — 删除数据目录
  3. 移除 WAL 归档 — 删除主机上的所有 WAL 文件
  4. 移除备份 stanza — 删除该实例的 pgBackRest 仓库 stanza

重建实例

销毁后,可以重新创建实例以获得全新开始:

# 销毁并清理所有数据
pg destroy -i proj01 --clean-data

# 使用相同名称重新创建
pg create -i proj01 --base-dir /data/pg

# 启动新实例
pg start -i proj01

重要: 不使用 --clean-data 时,旧的数据目录会被保留。当你重新创建实例时,PostgreSQL 会使用现有数据,而 init.sh(创建用户和设置管理员密码)不会再次运行。这可能导致以下问题:

  • 数据是用不同的用户或密码创建的
  • 你更改了配置中的默认用户
  • 你想要完全全新开始

需要彻底清理时使用 --clean-data

确认提示

默认情况下,pg destroy 会在执行前询问确认:

$ pg destroy -i proj01
!  This will destroy instance "proj01":
   - Container: pgcli-pg-default-proj01
   - Data dir: /data/pg/proj01 (preserved)

Continue? [y/N]:

使用 --force 跳过提示:

pg destroy -i proj01 --force

使用场景

1. 配置更改后的干净重启

如果你更改了配置中的默认用户、密码或 PostgreSQL 版本,销毁并重建:

# 更新配置
# 编辑 ~/.pgcli/pg.yaml 更改 postgres.user 或 image_tag

# 销毁并清理数据
pg destroy -i proj01 --clean-data --force

# 使用新设置重新创建
pg create -i proj01 --base-dir /data/pg
pg start -i proj01

2. 移除测试实例

测试完成后,移除不再需要的实例:

pg destroy -i test-instance --force

3. 修复损坏的状态

如果实例处于异常状态(例如启动失败、数据损坏),销毁并重建:

pg destroy -i broken-instance --clean-data --force
pg create -i broken-instance --base-dir /data/pg
pg start -i broken-instance

4. 释放资源

销毁不再活跃使用的实例以释放:

  • Podman 容器(CPU 和内存)
  • 主机磁盘空间(使用 --clean-data
  • 配置文件中的条目

与副本的关系

销毁副本时,主实例上的复制槽不会自动移除。你需要单独清理:

# 步骤 1:销毁副本
pg destroy -i ro1 --force

# 步骤 2:在主实例上删除复制槽
pg replica drop ro1 -i primary-instance

这种两步流程确保如果你计划稍后重新创建副本,不会意外丢失复制槽。

安全注意事项

  • 数据丢失--clean-data 会永久删除该实例的所有数据、WAL 和备份。谨慎使用。
  • 无法撤销:销毁后,除非有外部备份,否则实例无法恢复。
  • 配置丢失:实例条目会从配置文件中移除。如果以后需要该配置,请先备份。

标志

标志 默认值 描述
--force false 跳过确认提示
--clean-data false 同时移除主机数据、WAL 归档和备份 stanza
-i, --instance default 要销毁的实例名称

相关命令

  • pg create — 创建新实例
  • pg start — 启动实例
  • pg stop — 停止实例(容器保留)
  • pg replica drop — 从主实例移除复制槽