PostgreSQL 数据库恢复
1.目的与适用范围
本文件规范了在腾讯云 Ubuntu Server 24 LTS 上,使用 Docker 容器运行的 PostgreSQL 数据库实例发生故障、数据误操作或版本迁移时,通过标准化 Shell 脚本进行完整数据库恢复的操作流程。
适用范围包括:
灾难恢复(数据库损坏、容器意外删除)
数据回滚(误删数据、业务逻辑错误导致数据污染)
环境迁移(开发→测试→生产环境数据同步)
版本升级前的数据验证恢复测试
本操作将彻底删除并重建目标数据库,执行前请务必完成第 2 节的所有前置条件确认,操作不可逆。
2.前置条件与风险提示
执行恢复操作前,运维人员须逐一确认以下条件:
确认备份文件
postgres_backup_20260210_172517.sql.gz完整存在于备份目录Docker 容器
TestContainer处于正常运行状态(docker ps可见)密码文件
/opt/secrets/postgres-password.txt存在且权限为600或400宿主机备份目录
/data/backups/存在且有写入权限目标数据库
TestDB上已无业务高峰期流量
恢复过程中脚本会强制断开目标数据库的所有连接,并在重建前彻底删除现有数据库。所有未提交的事务将丢失。请确保关键数据已完成备份后再执行。
3.关键配置参数说明
脚本顶部的"配置区域"集中声明了所有关键变量。修改脚本适配自己的环境时,只需修改这一部分,其他代码保持不变。
# ==================== 配置区域 ====================
CONTAINER_NAME="TestContainer"
DB_USER="TestUser"
TARGET_DB="TestDB"
BACKUP_DIR="/data/backups"
DEFAULT_BACKUP_FILE="${BACKUP_DIR}/postgres_backup_20260210_172517.sql.gz"
SECRET_FILE="/opt/secrets/postgres-password.txt"
LOG_FILE="${BACKUP_DIR}/restore.log"CONTAINER_NAME(容器名称)
示例值:
TestContainer含义:目标 Docker 容器的名称,脚本将在此容器内执行数据库恢复操作。
获取方式:在宿主机上运行
docker ps可列出所有运行中容器的名称,从中选取对应的容器名填入。
DB_USER(数据库用户)
示例值:
TestUser含义:用于连接 PostgreSQL 数据库的用户名。该用户必须具备超级管理员权限(
SUPERUSER),因为恢复过程需要先删除目标数据库再重建,普通用户无法执行此类操作。注意事项:请确认该用户存在且权限正确,否则脚本将因权限不足而失败。
TARGET_DB(目标数据库名)
示例值:
TestDB含义:需要恢复的目标数据库名称。脚本执行时会先执行
DROP DATABASE删除同名库(若存在),然后从备份文件中重新创建并恢复数据。注意事项:数据库名严格区分大小写,请务必与备份时的名称保持一致。
BACKUP_DIR(备份目录)
示例值:
/data/backups含义:宿主机上用于存放备份文件(
.sql.gz)和日志文件的目录路径。脚本需要从此目录读取备份文件,并向此目录写入恢复日志。前置条件:确保该目录已存在,且运行脚本的用户对其拥有读取和写入权限。
DEFAULT_BACKUP_FILE(默认备份文件)
示例值:
postgres_backup_20260210_172517.sql.gz含义:在执行脚本时不指定任何参数时所使用的默认备份文件。该文件应位于
BACKUP_DIR目录下。使用方式:若直接运行脚本(无额外参数),将自动选取该文件进行恢复;若指定了备份文件参数,则忽略此默认值。
SECRET_FILE(密码文件)
示例值:
/opt/secrets/postgres-password.txt含义:存储数据库用户密码的文件路径,密码以明文形式保存在该文件内。脚本会读取此文件内容作为
DB_USER的登录密码。安全要求:该文件的权限必须设置为
600(仅所有者可读可写),以防止其他用户窥探密码。可使用chmod 600 /opt/secrets/postgres-password.txt进行设置。
LOG_FILE(日志文件)
示例值:
/wjw/data/backups/restore.log含义:脚本运行日志的输出文件,详细记录每一步操作的时间戳、执行命令和结果状态。用于事后审计、故障排查以及确认恢复是否成功。
位置说明:通常与备份文件存放在同一目录(即
BACKUP_DIR下),确保目录可写。
4.脚本基础函数与安全机制详解
4.1 脚本安全模式 — set -Eeuo pipefail
#!/bin/bash
set -Eeuo pipefail-E —— 错误陷阱继承
让 trap ... ERR 陷阱能捕获函数、source 引入脚本等子环境中的错误,确保清理逻辑在任何位置都能触发。未加 -E 时,函数内部的错误会静默跳过陷阱,导致资源残留或回滚失效。
-e —— 遇错立即退出
任何命令返回非零退出码时脚本立即终止,阻断错误向后蔓延。需注意 if、while 条件及管道默认不触发,通常需配合 pipefail 使用,否则管道中的失败可能被忽略。
-u —— 未定义变量报错
引用未赋值的变量时立即报错并退出。主要防范变量名拼写错误,避免将空值当作有效输入(如 $BACKUP_DRI 误写导致 rm -rf / 等灾难),让错误尽早暴露。
-o pipefail —— 管道错误传递
改变管道退出码规则:任一命令失败即让整个管道返回失败,而非仅看最后一个命令。这使得 -e 可以感知管道内部的错误并退出,防止 grep 失败后数据缺失仍继续执行。
这四个参数组合是生产环境 Shell 脚本的"保险丝"。没有它们,某个步骤失败了脚本可能继续执行,导致数据被错误覆盖。有了它们,任何异常都会让脚本立即停下来报警。
4.2 log() — 带时间戳的日志函数
log() {
echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"
}$(date '+%F %T') —— 生成时间戳
这是命令替换,执行 date 并返回格式化时间。%F 输出完整日期(如 2026-02-10),%T 输出时分秒(如 17:25:17),中间用空格分隔,最终生成形如 2026-02-10 17:25:17 的标准日志时间前缀。
$* —— 捕获所有日志内容
$* 代表传给函数(如 log)的所有参数,并将它们合并成一个字符串。无论参数中包含空格还是多个单词,$* 都能完整保留,直接作为日志消息的主体,避免因参数拆分导致信息丢失。
tee -a "$LOG_FILE" —— 双输出并追加
tee 将管道内容同时输出到终端(便于实时查看)和指定日志文件。-a 确保日志以追加模式写入,不会覆盖已有记录。结合前两部分,一条典型的日志语句为 echo "$(date '+%F %T') $*" | tee -a "$LOG_FILE",实现带时间戳的日志消息在屏幕和文件中同步持久化。
调用示例:log "备份文件: ${BACKUP_FILE}" 将在终端和日志文件中同时输出 [2026-02-10 17:25:17] 备份文件: /data/backups/xxx.sql.gz
4.3 cleanup() + trap — 退出时的自动清理机制
清理函数与信号捕获
cleanup() {
if [[ -n "${ENV_FILE:-}" && -f "$ENV_FILE" ]]; then
rm -f "$ENV_FILE"
log "已清理临时密码环境文件。"
fi
}
trap cleanup EXITtrap cleanup EXIT —— 注册退出钩子
脚本退出前(无论正常结束、出错退出或收到 Ctrl+C 中断信号)都会自动执行 cleanup 函数。这确保了清理操作绝不会被遗漏。
-n "${ENV_FILE:-}" —— 安全非空检查
检查变量 ENV_FILE 是否已定义且非空。:- 语法在变量未定义时返回空字符串,避免在 set -u 模式下因引用未定义变量而导致脚本报错终止,让后续逻辑可安全地处理空值情况。
-f "$ENV_FILE" —— 文件存在判断
确认 ENV_FILE 指向的路径是一个存在的常规文件。该判断常作为删除前的前置条件,避免对不存在的文件执行操作时产生不必要的错误信息。
rm -f "$ENV_FILE" —— 强制删除文件
强制删除临时密码文件。-f 选项表示即使文件不存在也不报错,静默完成清理,确保清理流程不会因目标文件缺失而中断。
脚本会创建一个包含数据库密码的临时文件(见第 4.4 节)。如果脚本中途崩溃,没有清理机制的话,这个含密码的文件会一直残留在/tmp/目录中,造成安全隐患。trap确保无论如何退出,密码文件都会被删除。
4.4 密码安全处理 — 避免密码出现在进程列表
密码读取与临时文件创建
# 检查密码文件权限
file_perm=$(stat -c %a "$SECRET_FILE" 2>/dev/null || echo "unknown")
if [[ "$file_perm" != "600" && "$file_perm" != "400" ]]; then
log "安全提醒:密码文件权限为 $file_perm,建议设为 600 或 400。"
fi
# 读取密码并创建临时环境变量文件
DB_PASSWORD=$(<"$SECRET_FILE")
ENV_FILE=$(mktemp /tmp/pg_restore_env.XXXXXX)
echo "PGPASSWORD=${DB_PASSWORD}" > "$ENV_FILE"
chmod 600 "$ENV_FILE"权限数字检查 —— stat -c %a "$SECRET_FILE"
使用 stat 命令查看文件的八进制权限值。-c %a 表示仅输出访问权限的数字部分(如 600),便于脚本直接比对。600 代表仅所有者拥有读写权限,同组和其他用户均无任何权限。
静默错误输出 —— 2>/dev/null
将标准错误重定向到 /dev/null 这个“黑洞”设备。即使前面的 stat 命令因文件不存在等原因执行失败,错误信息也不会显示在终端上,保持脚本输出干净。
高效文件读取 —— DB_PASSWORD=$(<"$SECRET_FILE")
将密码文件的内容读取到变量 DB_PASSWORD 中。< 是输入重定向,$(...) 是命令替换,这行代码的效果等同于 cat,但无需启动子进程,是 Bash 内置的高效写法。
安全临时文件 —— mktemp /tmp/pg_restore_env.XXXXXX
在 /tmp/ 目录下创建一个随机命名的临时文件。模板中的 X 会被替换为随机字符,确保文件名唯一,避免多实例并发运行时的文件名冲突。
环境变量传密 —— PGPASSWORD=
设置 PostgreSQL 的环境变量。psql 等客户端工具会自动读取此变量作为连接密码,从而避免在命令行参数中明文传递密码——否则通过 ps aux 即可直接看到密码,造成安全隐患。
锁定文件权限 —— chmod 600 "$ENV_FILE"
将临时密码文件的权限严格限制为 600。这意味着仅文件所有者可以读写,彻底杜绝同服务器上其他用户查看密码的可能性,是密码文件安全性的最后一道防线。
4.5 命令行参数解析 — 灵活指定备份文件
BACKUP_FILE="${1:-$DEFAULT_BACKUP_FILE}"
if [[ ! -f "$BACKUP_FILE" ]]; then
log "错误:备份文件 ${BACKUP_FILE} 不存在,恢复终止。"
usage
fi${1:-$DEFAULT_BACKUP_FILE} —— 参数默认值
$1 代表脚本的第一个命令行参数。:- 语法实现默认值逻辑:当用户未传入任何参数($1 为空)时,自动取 $DEFAULT_BACKUP_FILE 的值。这样既支持通过参数指定自定义备份文件,也允许无参数运行时直接使用预设的默认文件。
! -f "$BACKUP_FILE" —— 文件缺失判断
-f 检查指定路径是否为存在的普通文件,前面的 ! 对结果取反。因此整个条件“当文件不存在”时成立,脚本随即输出错误信息并退出。这保证了后续操作必定基于真实存在的备份文件,避免因文件缺失导致恢复失败。
使用方式:
# 方式一:使用默认备份文件
sudo bash restore.sh
# 方式二:指定备份文件
sudo bash restore.sh /wjw/data/backups/postgres_backup_20260210_172517.sql.gz5.恢复流程逐行解析
脚本将恢复操作分为四个标准阶段,下面按阶段详细讲解每行命令的作用。
恢复前全集群快照(安全网)
在任何破坏性操作之前,先将整个 PostgreSQL 实例的所有数据库导出为一份完整的快照。一旦恢复失败,可用此快照进行二次救援。
原子性删除目标数据库
安全地断开所有连接、禁止新连接,然后彻底删除旧数据库,为后续恢复腾出干净的空间。
执行数据恢复
将 gzip 压缩的备份文件解压,通过管道流式传输给 PostgreSQL,重建目标数据库的全部结构和数据。
恢复后增强验证
验证目标库是否存在、统计用户表数量,确保恢复结果符合预期,给运维人员提供可量化的确认依据。
5.1 恢复前全集群快照(pg_dumpall)
docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
pg_dumpall -c -U "$DB_USER" > "${BACKUP_DIR}/pre_restore_$(date +%F_%H%M%S).sql"在运行容器内执行命令 —— docker exec
在已存在的容器内部执行指定命令,不会创建新容器。与 docker run 不同,它直接进入目标容器的现有运行环境,适合在数据库服务已启动的容器中执行维护操作。
注入环境变量文件 —— --env-file "$ENV_FILE"
将指定的环境变量文件内容注入容器执行环境。此文件中通常包含 PGPASSWORD=xxx,使容器内的 PostgreSQL 客户端工具能自动读取密码,避免交互式输入或命令行明文传参。
"$CONTAINER_NAME" —— 指定目标容器
目标容器的名称变量,对应示例值 TestDB。它告诉 docker exec 在哪个容器内执行后续命令,确保操作作用于正确的数据库实例。
pg_dumpall —— 全量备份工具
PostgreSQL 官方提供的全实例备份工具。它导出整个数据库集群的所有内容,包括所有数据库、角色和表空间等全局对象,区别于仅备份单个数据库的 pg_dump,适合做完整的灾难恢复快照。
-c —— 添加清理语句
--clean 的简写。在生成的 SQL 备份文件头部自动添加 DROP 语句。恢复时,脚本会先清理已存在的同名对象再创建,避免因对象冲突导致恢复失败。
-U "$DB_USER" —— 指定连接用户
指定连接 PostgreSQL 数据库的用户名,此处使用变量 $DB_USER(如 blogjob)。该用户需具备足够的权限读取所有数据库和全局对象,否则备份可能不完整。
> "${BACKUP_DIR}/pre_restore_..." —— 输出重定向到时间戳文件
将 pg_dumpall 的输出重定向到文件中,而非显示在终端。文件名包含当前时间戳(通常由 $(date ...) 生成),使每次操作都生成唯一命名的快照文件,防止相互覆盖,便于追溯不同时间点的数据状态。
为什么要在恢复前再备份一次?
备份文件可能是几小时甚至几天前生成的,而生产数据库中可能已经有了新数据。这份"恢复前快照"捕获了最新状态,如果恢复完成后发现备份文件本身有问题,还可以用这份快照进行回滚,是双重保险。
5.2 原子性删除目标数据库
此阶段分三个子步骤,逐步"优雅地"清场,避免暴力 Kill 导致数据损坏。
5.2.1 禁止新连接
docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -U "$DB_USER" -d postgres -c \
"ALTER DATABASE \"$TARGET_DB\" WITH ALLOW_CONNECTIONS = false;" 2>/dev/null || truepsql —— PostgreSQL 命令行客户端
PostgreSQL 自带的交互式终端工具,可执行 SQL 语句或管理命令。在这里用于执行一次性管理操作,完成后即退出。
-d postgres —— 连接系统数据库
指定连接到内置的 postgres 系统数据库,而非目标数据库 BLOGdb。因为要管理的数据库正处于连接状态,必须从其他数据库发起操作,才能修改目标库的连接许可。
-c "ALTER DATABASE ..." —— 执行禁止连接语句
通过 -c 提交一条 SQL 语句并立即退出。ALTER DATABASE ... ALLOW_CONNECTIONS = false 的作用是从数据库层面禁止任何新的客户端连接,为后续的数据库删除操作做准备,防止期间有新的会话接入。
|| true —— 容错保证脚本继续
逻辑“或”运算,强制整行命令的退出码为成功(0)。如果前面的 psql 命令因目标数据库不存在或其他原因失败,脚本不会因此中断(尤其在 set -e 模式下),确保后续清理步骤仍能正常执行。
5.2.2 终止现有连接
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = '$TARGET_DB' AND pid <> pg_backend_pid();pg_stat_activity —— 系统活动连接视图
PostgreSQL 内置的系统视图,实时记录所有活跃的数据库连接信息,包括每个连接的进程 ID(pid)、所属数据库、连接用户、当前执行的查询等,是诊断和管控连接的核心数据来源。
WHERE datname = '$TARGET_DB' —— 筛选目标库会话
通过 WHERE 条件过滤 pg_stat_activity 视图,仅保留连接到目标数据库(如 BLOGdb)的会话记录,确保后续操作精确作用于该库的连接,不会误伤其他数据库的会话。
AND pid <> pg_backend_pid() —— 排除当前会话
pg_backend_pid() 返回当前执行这条 SQL 的连接的进程 ID。加上 pid <> 这个条件,就把自己从待终止列表中排除,避免 kill 掉自身导致操作中断,保证清理流程能完整执行下去。
pg_terminate_backend(pid) —— 强制终止连接
PostgreSQL 内置函数,向指定 pid 的会话发送终止信号,强制断开连接。效果类似于操作系统的 kill 命令,确保在删除数据库前没有任何残留连接占用,为 DROP DATABASE 扫清障碍。
5.2.3 带重试机制的删除
for i in {1..3}; do
if docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS \"$TARGET_DB\";"; then
log "✔ 数据库 $TARGET_DB 已成功删除。"
break
else
log "删除尝试 $i 失败,2 秒后重试..."
sleep 2
fi
donefor i in {1..3} —— 构建重试循环
for 循环配合大括号序列展开 {1..3},生成 1、2、3 三次迭代,为删除操作提供最多三次尝试机会,以应对瞬时的连接残留问题。
DROP DATABASE IF EXISTS —— 幂等删除
删除指定数据库。IF EXISTS 确保当目标库不存在时不会报错,直接返回成功,避免脚本因重复删除而异常中断,保证操作可安全重复执行。
"$TARGET_DB" —— 引号保护数据库名
用双引号包裹变量,使数据库名中的大写字母和特殊字符原样传递。若不加引号,PostgreSQL 默认将未引用的标识符转为小写,可能导致大小写敏感的名称无法匹配。
break —— 成功跳出循环
当 DROP DATABASE 执行成功(退出码为 0)时,break 立即终止当前循环,不再进行后续重试,避免不必要的重复操作。
sleep 2 —— 重试前等待
每次删除失败后暂停 2 秒再进入下一次循环。这为已被终止的连接提供清理缓冲时间,防止立即重试时仍有残留会话导致删除再次失败。
5.3 执行数据恢复
gunzip -c "$BACKUP_FILE" | \
docker exec -i --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -X -U "$DB_USER" -d postgresgunzip -c "$BACKUP_FILE" —— 流式解压
解压 .gz 压缩的备份文件。-c 参数将解压后的 SQL 内容直接输出到标准输出(管道),而非写入磁盘。这样避免了产生临时解压文件,不占用额外磁盘空间,尤其适合大体积备份。
|(管道)—— 数据流传递
将前一个命令的标准输出直接传递给后一个命令作为标准输入。这里构成的数据链路是:解压的 SQL 内容从宿主机流出,经由管道注入容器内的 psql,实现无中间文件的高效传输。
\(续行符)—— 长命令换行
Shell 中的行尾续行符,将一条很长的命令拆分为多行书写,仅提升脚本可读性,实际执行效果与单行命令完全一致,不产生任何功能差异。
docker exec -i —— 保持标准输入开放
在容器内执行命令时,-i 参数保持标准输入通道开启,使容器内的 psql 能够持续接收来自宿主管道的 SQL 数据流。若缺少此参数,管道内容无法传入容器,恢复操作将失败。
psql -X —— 忽略个人配置文件
启动 psql 时加上 -X,禁止读取用户主目录下的 .psqlrc 配置文件。这确保恢复行为完全由脚本控制,不受用户个人的个性化设置(如自动提交、输出格式等)干扰,保证结果可预期、可复现。
-d postgres —— 以系统库为入口
指定连接到 postgres 系统数据库作为执行入口。因为备份文件由 pg_dumpall 生成,其中包含 CREATE DATABASE 语句,psql 执行过程中会根据 SQL 指令自动切换到对应数据库进行恢复,无需手动连接目标库。
流式管道的优势
gunzip -c | docker exec -i这种"流式"方式的优势在于:备份文件可能有数 GB,如果先完整解压再导入,需要临时占用相同大小的磁盘空间。流式管道边解压边导入,宿主机磁盘只需存储压缩包本身。
5.4 恢复后增强验证
5.4.1 确认目标库存在
if docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -X -U "$DB_USER" -d postgres -tAc \
"SELECT 1 FROM pg_database WHERE datname='$TARGET_DB';" | grep -q 1; then
log "✔ 目标数据库 $TARGET_DB 存在。"
else
log "✘ 错误:目标数据库 $TARGET_DB 未找到,恢复可能失败!"
exit 1
fi-tAc "SELECT 1..." —— 纯粹结果输出
三个 psql 参数的组合:-t 仅输出数据行,去掉列名和行数统计;-A 取消对齐模式,去除多余空格;-c 执行后续 SQL 并立即退出。三者叠加让输出只有纯粹的 1 或空字符串,无任何格式干扰,便于脚本直接判断。
pg_database —— 系统数据库目录
PostgreSQL 内置的系统目录表,记录了实例中所有数据库的信息,其中 datname 字段即为数据库名称。通过查询该表可确认目标库是否已成功创建并存在。
grep -q 1 —— 静默状态码判断
grep 的 -q 模式不输出任何文本,仅根据是否在输入中找到 1 来设置退出码。找到则返回 0(成功),否则返回 1(失败),可直接用于 if 条件判断,无需额外解析输出内容。
exit 1 —— 失败信号传递
以退出码 1 终止脚本执行,明确向调用方或自动化平台报告“恢复失败”。非零退出码是脚本与外部交互的标准失败信号,便于触发后续告警或流程中断。
5.4.2 统计用户表数量
table_count=$(docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -X -U "$DB_USER" -d "$TARGET_DB" -tAc \
"SELECT count(*) FROM information_schema.tables
WHERE table_schema NOT IN ('pg_catalog','information_schema');")
log "✔ $TARGET_DB 中包含 $table_count 个用户表。"
information_schema.tables —— 标准表信息视图
这是 SQL 标准定义的系统视图,存放数据库中所有表和视图的元数据。它跨数据库兼容,在 PostgreSQL 中可查询当前库内全部表对象,是统计业务表数量的数据来源。
table_schema NOT IN (...) —— 排除系统 Schema
通过过滤 table_schema 字段,排除 pg_catalog(PostgreSQL 内部系统表)和 information_schema(标准系统视图)这两个内置 Schema。这样统计结果仅包含用户自定义的业务表,不受系统对象干扰。
如何判断恢复是否正确?
在执行恢复前,记录下原始环境(或上次正常备份)的表数量。恢复后对比:如果表数量一致,说明数据结构完整恢复。脚本输出的table_count是关键质量指标。
6.完整脚本参考
以下为完整的生产就绪脚本,保存为 restore.sh,赋予执行权限 chmod +x restore.sh 后即可使用。
#!/bin/bash
set -Eeuo pipefail
# ==================== 配置区域(可被命令行参数覆盖) ====================
CONTAINER_NAME="TestContainer"
DB_USER="TestUser"
TARGET_DB="TestDB"
BACKUP_DIR="/data/backups"
DEFAULT_BACKUP_FILE="${BACKUP_DIR}/postgres_backup_20260210_172517.sql.gz"
SECRET_FILE="/opt/secrets/postgres-password.txt"
LOG_FILE="${BACKUP_DIR}/restore.log"
# ==================== 基础函数 ====================
log() {
echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"
}
cleanup() {
if [[ -n "${ENV_FILE:-}" && -f "$ENV_FILE" ]]; then
rm -f "$ENV_FILE"
log "已清理临时密码环境文件。"
fi
}
trap cleanup EXIT
# ==================== 解析命令行参数 ====================
BACKUP_FILE="${1:-$DEFAULT_BACKUP_FILE}"
if [[ ! -f "$BACKUP_FILE" ]]; then
log "错误:备份文件 ${BACKUP_FILE} 不存在,恢复终止。"
exit 1
fi
# ==================== 密码安全处理 ====================
if [[ ! -f "$SECRET_FILE" ]]; then
log "错误:密码文件 $SECRET_FILE 不存在,恢复终止。"
exit 1
fi
file_perm=$(stat -c %a "$SECRET_FILE" 2>/dev/null || echo "unknown")
if [[ "$file_perm" != "600" && "$file_perm" != "400" ]]; then
log "安全提醒:密码文件权限为 $file_perm,建议设为 600 或 400。"
fi
DB_PASSWORD=$(<"$SECRET_FILE")
ENV_FILE=$(mktemp /tmp/pg_restore_env.XXXXXX)
echo "PGPASSWORD=${DB_PASSWORD}" > "$ENV_FILE"
chmod 600 "$ENV_FILE"
log "===== 开始恢复操作 ====="
log "备份文件: ${BACKUP_FILE}"
log "目标容器: ${CONTAINER_NAME}"
log "目标数据库: ${TARGET_DB}"
# ==================== 1. 恢复前全集群快照 ====================
log "[1/4] 创建恢复前全集群快照..."
docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
pg_dumpall -c -U "$DB_USER" > "${BACKUP_DIR}/pre_restore_$(date +%F_%H%M%S).sql"
log "全集群快照已完成。"
# ==================== 2. 原子性删除目标库 ====================
log "[2/4] 原子化删除目标库 $TARGET_DB..."
docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -U "$DB_USER" -d postgres -c \
"ALTER DATABASE \"$TARGET_DB\" WITH ALLOW_CONNECTIONS = false;" 2>/dev/null || true
docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -U "$DB_USER" -d postgres -c "
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = '$TARGET_DB' AND pid <> pg_backend_pid();
" || true
sleep 1
for i in {1..3}; do
if docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS \"$TARGET_DB\";"; then
log "✔ 数据库 $TARGET_DB 已成功删除。"
break
else
log "删除尝试 $i 失败,2 秒后重试..."
sleep 2
fi
done
# ==================== 3. 执行恢复 ====================
log "[3/4] 开始将备份恢复到 PostgreSQL 集群..."
gunzip -c "$BACKUP_FILE" | \
docker exec -i --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -X -U "$DB_USER" -d postgres
log "恢复命令执行完毕。"
# ==================== 4. 恢复后验证 ====================
log "[4/4] 全面验证恢复结果..."
if docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -X -U "$DB_USER" -d postgres -tAc \
"SELECT 1 FROM pg_database WHERE datname='$TARGET_DB';" | grep -q 1; then
log "✔ 目标数据库 $TARGET_DB 存在。"
else
log "✘ 错误:目标数据库 $TARGET_DB 未找到,恢复可能失败!"
exit 1
fi
table_count=$(docker exec --env-file "$ENV_FILE" "$CONTAINER_NAME" \
psql -X -U "$DB_USER" -d "$TARGET_DB" -tAc \
"SELECT count(*) FROM information_schema.tables \
WHERE table_schema NOT IN ('pg_catalog','information_schema');")
log "✔ $TARGET_DB 中包含 $table_count 个用户表。"
log "恢复流程全部完成,数据库已可用。"7.常见故障排查
故障现象 | 可能原因 | 解决方法 |
|---|---|---|
备份文件不存在,恢复终止 | 路径错误或文件未传输到位 | 执行 |
密码文件不存在或权限不对 | 密码文件路径错误或未创建 | 执行 |
docker exec 报容器不存在 | 容器未启动或名称错误 | 执行 |
删除数据库失败:有其他会话连接 | 连接终止命令未完全生效 | 等待几秒后手动执行子步骤 2.2 的 SQL,或重启容器后再执行恢复 |
恢复后目标库不存在 | 备份文件损坏或备份时未包含该库 | 用 |
表数量与预期不符 | 备份文件不完整或版本不一致 | 对比原始备份时的日志记录,检查备份是否完整。可用 |
pg_dumpall 报认证失败 | 密码文件内容有误或用户权限不足 | 执行 |
8.恢复后验收清单
恢复完成后,运维人员须完成以下验收项目并签字确认:
日志文件
restore.log末尾显示"恢复流程全部完成,数据库已可用"日志中确认"✔ 目标数据库 TestDB存在"
日志中用户表数量与预期一致(与上次备份记录对比)
业务应用已重新连接数据库并正常读写(功能冒烟测试通过)
恢复前全集群快照文件
pre_restore_*.sql已生成并保留临时密码文件
/tmp/pg_restore_env.*已被自动清理(确认不存在)本次恢复操作已在运维工单系统中填写结果并关闭工单
操作完成后建议
将本次恢复的操作日志和日期记录到数据库变更台账中,便于日后审计。如发现任何异常,立即上报数据库负责人,不要自行尝试再次恢复。