PostgreSQL如何进行时间点恢复(PITR)

PostgreSQL 的时间点恢复基于一个文件系统级的物理备份(也可以叫做全量备份,下文称其为全量备份)和若干归档 WAL 日志来工作。当 PostgreSQL 崩溃,进行时间点恢复时,PostgreSQL 会先以全量备份作为起点进行启动,然后进入恢复模式,回放归档的 WAL 日志,以恢复到指定的某一时间点。所以,你可以恢复到【文件系统备份到数据库崩溃那一时刻】这一时间段的任意一个时间点。

这个全量备份可以是 pg_basebackup 工具自动做的全量备份,也可以是使用 pg_backup_startpg_backup_stop 函数手动做的全量备份。甚至可以是直接对 PostgreSQL 数据目录文件使用 cp 命令或者其它方法做的文件拷贝。这个全量备份没必要具有一致性状态,因为我们有完整的归档 WAL 日志,任何不一致都可以通过回放 WAL 日志来纠正。

举个例子,你在LSN 0/3000338 处做了一次 pg_basebackup,在LSN 0/4000078 处数据库崩溃了,所以理论上来说你可以恢复数据库到 [0/3000338, 0/4000078] 之间的任意时刻。

要想进行时间点恢复,分三步走:

  1. 设置 PostgreSQL 进入 WAL 归档模式。
  2. 进行全量备份。
  3. PostgreSQL 崩溃后,设置 PostgreSQL 进入 restore 模式,执行时间点恢复。

设置WAL归档模式

时间点恢复的关键是,你拥有一段完整的,连续的归档 WAL 日志,这段 WAL 日志的起点要小于等于你做文件系统物理备份的那一点。

PostgreSQL 生成的 WAL 日志以 segment 为单位,一个 WAL 日志文件就是一个 segment,他们是一对一的关系。一个 segment 默认为 16MB(initdb时可以指定 --wal-segsize 为其他大小),WAL 日志文件的命名方式是从 000000010000000000000001 开始递增。其中前八位表示 TimeLine,中间八位表示 LogID,最后八位表示 LogSeg。在不设置归档模式的情况下,WAL 日志文件是循环使用的,PostgreSQL 只会生成固定的几个 WAL 日志文件,然后通过将不再需要的 WAL 日志文件重命名为更高的编号来回收它们。

当设置了归档模式后,PostgreSQL 会在 WAL 日志文件被写满之后,被回收之前 ,将其保存到指定的位置。将 WAL 日志保存到何处完全由用户通过 archive_command 或者 archive_library 指定。要使用WAL归档模式,需要设置参数:

  1. wal_level 大于等于 replica级别。
  2. archive_mode 设置为 on。
  3. archive_command 参数中配置需要使用的 shell 命令,或者在 archive_library 参数中配置需要使用的动态库,告诉 PostgreSQL 如何归档 WAL 日志。

设置完上述参数后,需要重启 PostgreSQL ,然后 PostgreSQL 会启动一个 archiver 进程,持续进行 WAL 日志归档。

一个最简单的 archive_command 命令是:

shell 复制代码
archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'

%f 会被替换为实际的 WAL 日志文件名,%p 会被替换成时间的 WAL 日志文件路径。这个 shell 命令首先检测是否已经存在同名的 WAL 日志,如果不存在,才会拷贝 WAL 日志到目标位置。

如果 archive_command 返回非0,PostgreSQL 会认为归档失败,会定期重试。

另外,archive_command 的一个替代品是 archive_library,你需要自己编写 c 代码编译成动态库,完成实际的归档操作,可以参考 contrib/basic_archive 来编写你的代码。

注意: archiver 进程只会归档已经写满的 WAL 日志文件,因此事务提交到日志归档中间有一定的延时。你可以配置 archive_timeout 来每隔一段时间就切换一次 WAL 日志,或者手动调用 pg_switch_wal 函数,强制立即执行 WAL 日志的切换。

进行全量备份

使用 pg_basebackup 进行全量备份

可以使用 pg_basebackup 工具进行在线的全量备份,一个简单的例子是:

shell 复制代码
pg_basebackup -D /target_dir -h 127.0.0.1 -p 5432 --checkpoint=fast --wal-method=stream --verbose

pg_basebackup 需要最少保留 备份开始到备份结束 这一时间段产生的 WAL 日志来使备份达到一个一致性状态。

因此,在开启了归档模式的状态下,pg_basebackup 会在WAL归档目录下,创建一个备份历史文件(backup history file),比如 00000002000000000000000E.00000028.backup 就是一个备份历史文件,备份历史文件名包含三部分,第一部分是备份开始时的 WAL 日志文件名,第二部分是备份开始时的 LSN 在WAL 日志文件内的位置,第三部分是固定的 backup。备份历史文件内记录了备份时的一些信息:

![[Clipboard_Screenshot_1752674533.png]]

通过这个备份历史文件,你可以确定对于一个全量备份,需要从哪里开始进行恢复。

使用pg_backup_start函数进行全量备份

你还可以使用 pg_backup_startpg_backup_stop 这一对函数来进行全量备份,这基本上是对 pg_basebackup 所做事情的手工模拟,基本使用方法是:

shell 复制代码
0. 确保 PostgreSQL 处于归档模式
1. SELECT pg_backup_start(label => 'label', fast => false);
2. 使用文件系统备份工具,tar、cpio、cp等将数据目录进行备份
3. SELECT * FROM pg_backup_stop(wait_for_archive => true);
  1. pg_basebackup 一样,默认情况下,pg_backup_start 函数将等待 PostgreSQL 进行一次 checkpoint,你可以指定 fast 参数为 true,来请求立即执行一次 checkpoint,这相当于 pg_basebackup--checkpoint=fast 参数。label 参数指定此次执行全量备份的标签,用于标识这次全量备份,和 pg_basebackup--label=label 参数一样。
  2. 随便使用任何工具,对数据目录进行备份,备份期间没有必要停止服务器的运行。你也可以使用文件系统备份工具直接备份 PostgreSQL 的数据目录。注意不要备份 pg_wal/ 目录下的文件,postmaster.pid 文件和 postmaster.opts 文件。pg_replslot 目录下的文件也不要复制,因为如果你要用这个备份启动一个备库的话,复制槽会阻止未被客户端消费的WAL日志被删除,由于这些客户端实际上连接的是主库,新搭建备库的WAL日志无法被消费,会造成无限堆积;如果你要用这个备份启动一个新的主库,这些旧的复制槽也不会有太大作用,因为这些复制槽的内容太过落后了。pg_dynshmem/pg_notify/pg_serial/pg_snapshots/pg_stat_tmp/pg_subtrans/ 目录下的内容可以不用备份,因为它们会在 PostgreSQL 启动时初始化,但是目录本身需要保留。
  3. pg_backup_stop 会立即进行一次 WAL 日志的切换,这样 archiver 进程能够有机会尽快归档全量备份期间产生的 WAL 日志,如果 wait_for_archive 为 true,还会等待 archiver 进程归档 WAL 日志完成。

如下图,pg_backup_start 会返回备份的起点 LSN,pg_backup_stop 会返回三个字段,lsn代表备份的结束 LSN,labelfile字段应该被原封不动的写入备份根目录的 backup_label 文件中,spcmapfile 字段应该被原封不动的写入 tablespace_map 备份根目录的文件中。pg_basebackup 做出的全量备份会自动生成这两个文件,这里需要你自己生成。

进行时间点恢复

首先拿到你之前做的全量备份。

确保备份中的 pg_wal 文件夹是空的,因为这些 WAL 日志都是过时的WAL日志,检查原节点,如果有 WAL 日志还没来得及归档,你需要拷贝它到备份节点的 pg_wal 下面。

postgresql.conf 配置文件中设置一些配置:

  1. restore_command 告诉 PostgreSQL 如何从归档目录中获取到所需要的 WAL 日志文件和 .history 文件:
shell 复制代码
restore_command = 'cp /mnt/server/archivedir/%f %p'
  1. 设置一个停止点,告诉 PostgreSQL 何时停止恢复。如果不指定停止点,默认会回放完所有的 WAL 日志。可以指定下面任意一个配置:
shell 复制代码
recovery_target = 'immediate' # 只能指定immediate,它会回放完所有的WAL日志
recovery_target_name = 'xxx' # 还原到指定的还原点,这个还原点 可以用 pg_create_restore_point 函数创建
recovery_target_time = 'timestamp with time zone' # 还原到一个指定的时间
recovery_target_xid = 'xxx' # 还原到指定的事务。它只能保证所有在 xid = xxx 之前提交的事务被还原,不保证所有 xid 小于 xxx 的事务都能被还原。因为事务 ID 虽然是顺序分配的,但是他们提交的时间不是顺序的,而 WAL 日志只能顺序回放
recovery_target_lsn = 'xxx' # 还原到指定的 LSN

另外还有几个 GUC 和停止点相关:

  1. recovery_target_inclusive = 'on/off'。
    recovery_target_timerecovery_target_xidrecovery_target_lsn 配合使用,如果为 on,则还原时会包括你所指定的那一点,如果为 off,则不包括。
  2. recovery_target_timeline = 'latest/current/0x11'
    还原到指定的时间线。可以指定 lateset 还原到集群最近的一个时间线,也可以指定 current 还原到你做全量备份时的时间点,或者直接指定一个指定的时间线,比如 0x11。

%f%p 的含义和 archive_command 一样。如果在归档目录中找不到所需要的 WAL 日志,会去 pg_log 中寻找。

然后创建一个空的 recovery.signal 文件。然后启动 PostgreSQL

相关推荐
一 乐5 小时前
婚纱摄影网站|基于ssm + vue婚纱摄影网站系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端
1.14(java)6 小时前
SQL数据库操作:从CRUD到高级查询
数据库
Full Stack Developme7 小时前
数据库索引的原理及类型和应用场景
数据库
IDC02_FEIYA9 小时前
SQL Server 2025数据库安装图文教程(附SQL Server2025数据库下载安装包)
数据库·windows
辞砚技术录9 小时前
MySQL面试题——联合索引
数据库·面试
萧曵 丶9 小时前
MySQL 主键不推荐使用 UUID 的深层原因
数据库·mysql·索引
小北方城市网9 小时前
分布式锁实战指南:从选型到落地,避开 90% 的坑
java·数据库·redis·分布式·python·缓存
毕设十刻9 小时前
基于Vue的人事管理系统67zzz(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
TDengine (老段)11 小时前
TDengine Python 连接器入门指南
大数据·数据库·python·物联网·时序数据库·tdengine·涛思数据
萧曵 丶12 小时前
事务ACID特性详解
数据库·事务·acid