Skip to content

PostgreSQL 基于位置的恢复

WAL 位置(LSN)

基于位置的恢复依赖于 WAL(Write-Ahead Logging)机制,WAL 日志记录了所有数据修改操作。WAL 位置使用 LSN(Log Sequence Number)表示,LSN 是一个 64 位的数字,格式为 WAL_FILE_OFFSET,例如 0/12345678。LSN 单调递增,反映了 WAL 记录的顺序。可以通过以下方式获取当前 WAL 位置:

sql
-- 获取当前 WAL 位置
SELECT pg_current_wal_lsn();

-- 获取上一次检查点的 WAL 位置
SELECT pg_last_checkpoint_lsn();

-- 获取最后写入的 WAL 位置
SELECT pg_current_wal_write_lsn();

-- 获取最后发送的 WAL 位置(用于复制)
SELECT pg_current_wal_send_lsn();

-- 获取最后应用的 WAL 位置(用于复制)
SELECT pg_last_wal_replay_lsn();

恢复过程

基于位置的恢复过程包括以下步骤:

  1. 基础备份恢复:首先恢复最近的基础备份,将数据库恢复到备份时的状态
  2. WAL 日志重放:从基础备份完成后的 WAL 位置开始,重放 WAL 日志,直到指定的恢复目标
  3. 恢复完成处理:恢复到目标位置后,数据库进入正常运行状态或只读状态

配置与准备

基础配置

要启用基于位置的恢复,需要在 postgresql.conf 中配置以下参数:

ini
# 启用 WAL 归档
archive_mode = on

# 配置 WAL 归档命令
archive_command = 'cp %p /path/to/wal_archive/%f'  # Linux/Unix
archive_command = 'copy %p "C:\path\to\wal_archive\%f"'  # Windows

# 设置 WAL 级别为 replica 或 higher
wal_level = replica

# 设置 WAL 段文件大小(可选,默认 16MB)
wal_segment_size = 16MB

# 设置最大 WAL 发送器数量(用于复制)
max_wal_senders = 10

# 设置最大复制槽数量(用于复制)
max_replication_slots = 10

# 启用热备模式(用于从库)
hot_standby = on

基础备份

在进行基于位置的恢复之前,需要先创建基础备份。可以使用 pg_basebackup 工具创建基础备份:

bash
# 创建基础备份
pg_basebackup -h localhost -p 5432 -U postgres -D /path/to/backup -Ft -z -P --checkpoint=fast

# 查看备份信息
ls -la /path/to/backup

WAL 归档验证

确保 WAL 归档配置正确,并且 WAL 日志能够正常归档:

sql
-- 查看 WAL 归档状态
SELECT * FROM pg_stat_archiver;

-- 切换 WAL 段,触发归档
SELECT pg_switch_wal();

-- 验证 WAL 归档文件是否存在
ls -la /path/to/wal_archive/

恢复操作步骤

1. 停止数据库服务

在进行恢复操作之前,需要先停止 PostgreSQL 服务:

bash
# Linux/Unix
systemctl stop postgresql

# Windows
net stop postgresql-x64-14

# 或使用 pg_ctl
pg_ctl stop -D /path/to/data -m fast

2. 备份现有数据目录

为了安全起见,建议先备份现有数据目录:

bash
# Linux/Unix
mv /path/to/data /path/to/data_old_$(date +%Y%m%d%H%M%S)

# Windows
move "C:\path\to\data" "C:\path\to\data_old_$(date +%Y%m%d%H%M%S)"

3. 恢复基础备份

将基础备份恢复到数据目录:

bash
# 解压基础备份
mkdir -p /path/to/data
cd /path/to/data

# 如果是 tar 格式的备份
for file in /path/to/backup/*.tar.gz; do
    tar -xzf "$file"
done

# 设置文件权限(Linux/Unix)
chown -R postgres:postgres /path/to/data
chmod 700 /path/to/data

4. 配置恢复环境

创建 recovery.signal 文件和 postgresql.auto.conf 文件,配置恢复目标和恢复命令:

bash
# 创建 recovery.signal 文件
touch /path/to/data/recovery.signal

# 配置 postgresql.auto.conf 文件
cat > /path/to/data/postgresql.auto.conf << EOF
# WAL 恢复命令
restore_command = 'cp /path/to/wal_archive/%f %p'  # Linux/Unix
# restore_command = 'copy "C:\path\to\wal_archive\%f" "%p"'  # Windows

# 恢复目标设置
recovery_target = 'immediate'  # 恢复到最近的一致状态
# 或恢复到特定 WAL 位置
# recovery_target_lsn = '0/12345678'
# 或恢复到特定时间点
# recovery_target_time = '2024-06-15 14:30:00'

# 恢复目标动作:promote(自动切换到正常模式)或 pause(保持只读模式)
recovery_target_action = 'promote'

# 恢复目标时间线:latest(使用最新时间线)
recovery_target_timeline = 'latest'
EOF

5. 启动数据库服务

恢复配置完成后,启动 PostgreSQL 服务开始恢复:

bash
# Linux/Unix
systemctl start postgresql

# Windows
net start postgresql-x64-14

# 或使用 pg_ctl
pg_ctl start -D /path/to/data -l /path/to/log/postgresql.log

6. 监控恢复进度

可以通过查看日志文件或使用 SQL 命令监控恢复进度:

bash
# 查看恢复日志
tail -f /path/to/log/postgresql.log
sql
-- 连接到数据库(需要在恢复完成后)
psql -h localhost -p 5432 -U postgres

-- 查看恢复状态
SELECT pg_is_in_recovery();  -- 恢复中返回 true,完成后返回 false

-- 查看最后恢复的 WAL 位置
SELECT pg_last_wal_replay_lsn();

-- 查看最后恢复的时间
SELECT pg_last_xact_replay_timestamp();

恢复目标设置

恢复到特定 WAL 位置

可以通过 recovery_target_lsn 参数指定要恢复到的 WAL 位置:

ini
recovery_target_lsn = '0/12345678'

获取 WAL 位置的方法:

sql
-- 获取当前 WAL 位置
SELECT pg_current_wal_lsn();

-- 从 pg_wal_replay_hisory 视图获取历史 WAL 位置
SELECT * FROM pg_wal_replay_hisory ORDER BY end_lsn DESC LIMIT 10;

恢复到特定时间点

可以通过 recovery_target_time 参数指定要恢复到的时间点:

ini
recovery_target_time = '2024-06-15 14:30:00'

恢复到特定事务

可以通过 recovery_target_xid 参数指定要恢复到的事务 ID:

ini
recovery_target_xid = '123456'

恢复目标动作

recovery_target_action 参数指定恢复到目标位置后的动作:

  • promote:自动切换到正常运行模式
  • pause:保持只读模式,需要手动执行 pg_promote() 切换到正常模式
  • shutdown:恢复完成后关闭数据库
ini
recovery_target_action = 'promote'

高级恢复配置

时间线管理

时间线是 PostgreSQL 用于管理恢复历史的机制。每次进行基础备份或恢复操作时,都会创建新的时间线。recovery_target_timeline 参数用于指定要恢复到的时间线:

  • latest:恢复到最新的时间线(默认)
  • current:恢复到当前时间线
  • 具体的时间线 ID:恢复到指定的时间线
ini
recovery_target_timeline = 'latest'

部分恢复

在某些情况下,可能只需要恢复特定的表或数据库。虽然 PostgreSQL 不直接支持单表恢复,但可以通过以下方法实现:

  1. 使用基于位置的恢复将整个数据库恢复到临时服务器
  2. 从临时服务器导出需要恢复的表
  3. 将导出的数据导入到生产服务器

恢复测试

定期测试恢复过程是确保恢复机制可靠的重要手段。测试内容包括:

  1. 基础备份的完整性
  2. WAL 归档的正确性
  3. 恢复过程的完整性
  4. 恢复后数据的一致性
bash
#!/bin/bash
# 恢复测试脚本示例

date

echo "1. 停止测试数据库服务"
systemctl stop postgresql-test

BACKUP_DIR="/backup/postgresql/test"
DATA_DIR="/var/lib/postgresql/14/test"
WAL_ARCHIVE="/backup/wal_archive/test"
LOG_FILE="/var/log/postgresql/test/postgresql.log"

# 清理旧数据目录
echo "2. 清理旧数据目录"
mv $DATA_DIR ${DATA_DIR}_old_$(date +%Y%m%d%H%M%S) || true
mkdir -p $DATA_DIR

# 恢复基础备份
echo "3. 恢复基础备份"
cd $DATA_DIR
for file in $BACKUP_DIR/*.tar.gz; do
    echo "Extracting $file"
    tar -xzf "$file"
done

# 配置恢复环境
echo "4. 配置恢复环境"
touch $DATA_DIR/recovery.signal

cat > $DATA_DIR/postgresql.auto.conf << EOF
restore_command = 'cp $WAL_ARCHIVE/%f %p'
recovery_target = 'immediate'
recovery_target_action = 'promote'
recovery_target_timeline = 'latest'
EOF

# 设置权限
chown -R postgres:postgres $DATA_DIR
chmod 700 $DATA_DIR

# 启动测试数据库
echo "5. 启动测试数据库"
systemctl start postgresql-test

# 等待恢复完成
echo "6. 等待恢复完成"
sleep 10

# 验证恢复结果
echo "7. 验证恢复结果"
psql -h localhost -p 5433 -U postgres -c "SELECT version();"
psql -h localhost -p 5433 -U postgres -c "SELECT pg_is_in_recovery();"
psql -h localhost -p 5433 -U postgres -c "SELECT count(*) FROM pg_stat_user_tables;"

# 清理测试数据
echo "8. 清理测试数据"
systemctl stop postgresql-test
rm -rf $DATA_DIR

# 恢复原始数据
echo "9. 恢复原始数据"
mv ${DATA_DIR}_old_$(date +%Y%m%d%H%M%S) $DATA_DIR
systemctl start postgresql-test

echo "Recovery test completed at $(date)"

常见问题处理

恢复失败

恢复失败可能由多种原因导致,包括:

  1. WAL 日志缺失:检查 WAL 归档目录中是否存在所需的 WAL 日志文件
  2. 恢复命令错误:检查 restore_command 配置是否正确
  3. 权限问题:确保 PostgreSQL 用户对 WAL 归档目录和数据目录有读写权限
  4. 配置文件错误:检查 postgresql.auto.conf 文件中的恢复参数配置

恢复时间过长

恢复时间过长可能由以下原因导致:

  1. 大量 WAL 日志:重放大量 WAL 日志需要时间
  2. 硬件性能不足:磁盘 I/O、CPU 或内存性能不足
  3. 恢复目标设置不当:恢复到过远的时间点

数据一致性问题

恢复后数据不一致可能由以下原因导致:

  1. WAL 日志损坏:WAL 日志文件损坏或不完整
  2. 恢复过程中断:恢复过程中数据库服务被中断
  3. 基础备份损坏:基础备份文件损坏或不完整

最佳实践

1. 定期备份与测试

  • 定期创建基础备份,建议每天至少一次
  • 定期测试恢复过程,确保恢复机制可靠
  • 验证备份的完整性和一致性

2. WAL 归档管理

  • 配置可靠的 WAL 归档机制
  • 定期清理过期的 WAL 日志
  • 监控 WAL 归档状态,确保归档正常

3. 恢复目标规划

  • 根据业务需求规划恢复目标(时间点或 WAL 位置)
  • 记录关键操作的时间点或 WAL 位置
  • 建立恢复操作手册,包括详细的恢复步骤

4. 自动化管理

  • 使用脚本自动化基础备份和 WAL 归档
  • 自动化恢复测试过程
  • 监控恢复相关的指标和状态

5. 监控与告警

  • 监控 WAL 归档状态
  • 监控基础备份的完整性
  • 配置恢复相关的告警规则

常见问题(FAQ)

Q1: 如何获取特定操作的 WAL 位置?

A1: 可以通过以下方式获取特定操作的 WAL 位置:

sql
-- 在执行操作前获取当前 WAL 位置
SELECT pg_current_wal_lsn();

-- 执行需要记录的操作
INSERT INTO test_table VALUES (1, 'test');

-- 获取操作后的 WAL 位置
SELECT pg_current_wal_lsn();

-- 或者使用日志记录
SET log_statement = 'all';
-- 执行操作,然后查看日志文件中的 WAL 位置

Q2: 如何处理 WAL 日志缺失?

A2: 如果 WAL 日志缺失,恢复过程将失败。处理方法包括:

  1. 检查 WAL 归档目录中是否存在所需的 WAL 日志文件
  2. 检查归档命令是否正确配置
  3. 如果 WAL 日志确实缺失,可能需要从更早的基础备份开始恢复
  4. 考虑使用 PITR 结合逻辑备份进行恢复

Q3: 恢复过程中可以取消吗?

A3: 恢复过程可以通过停止数据库服务来取消,但这可能导致数据库处于不一致状态。建议在恢复开始前仔细规划恢复目标,避免不必要的取消操作。

Q4: 如何恢复到特定的事务?

A4: 可以通过 recovery_target_xid 参数恢复到特定的事务 ID:

ini
recovery_target_xid = '123456'

事务 ID 可以从日志文件或系统视图中获取:

sql
-- 查看当前事务 ID
SELECT txid_current();

-- 查看最近的事务 ID
SELECT * FROM pg_stat_activity WHERE state = 'active';

Q5: 恢复后如何验证数据一致性?

A5: 恢复后验证数据一致性的方法包括:

  1. 执行数据库完整性检查
  2. 验证关键表的数据量和内容
  3. 运行应用程序的功能测试
  4. 执行查询验证数据关系
sql
-- 执行数据库完整性检查
SELECT pg_stat_get_db_corruption_stats();

-- 验证关键表的数据量
SELECT count(*) FROM critical_table;

-- 验证数据关系
SELECT count(*) FROM table1 t1 JOIN table2 t2 ON t1.id = t2.table1_id;

Q6: 基于位置的恢复和基于时间的恢复有什么区别?

A6: 基于位置的恢复和基于时间的恢复都是 PITR 的实现方式,区别在于恢复目标的指定方式:

  • 基于位置的恢复:使用 WAL 位置(LSN)作为恢复目标,精度更高
  • 基于时间的恢复:使用时间点作为恢复目标,使用更方便

在实际应用中,基于时间的恢复更常用,因为更容易记录和管理;而基于位置的恢复常用于需要精确恢复的场景,如恢复到特定操作之前。