PostgreSQL备份恢复 12.2-阿里云开发者社区

开发者社区> 数据库> 正文

PostgreSQL备份恢复 12.2

简介: 本文参考https://yq.aliyun.com/articles/59359

本文介绍 自定义表空间下,11版本、12.2版本、还原点,事务xid,时间点,多时间线的备份和恢复。

背景
冷备份:pg_dump,pg_dumpall备份出来都叫冷备份,也称之为逻辑备份,像oracle的数据泵备份出来的一样。是没有增量的概念的,如果数据库在运行过程中发生故障,使用逻辑备份只能将数据库还原到备份时刻,无法恢复到故障发生前的那个时刻。又或者在使用过程中由于误操作修改或删除了重要数据,需要还原到误操作前的那个时刻怎么办呢。
使用冷备份+wal归档文件可以实现任意时间点的恢复。但是冷备份需要停库操作,所以实用性不大。

热备份:在线数据库文件备份,可以在数据库正常使用中进行备份,且对数据库影响不大,同时又支持时间点的恢复。

前置条件:

1. 确保你打开了连续归档
2. 想做时间点恢复,必须对数据库服务器进行一次基础备份(连续归档备份)。

基础备份有两种方法可以做。
(1)pg_basebackup
(2)pg_start_backup/pg_stop_backup

可以在本笔记中搜索相关资料打开归档,并完成一次基础备份(又称之为连续归档备份)。
或者看下面的步骤

开始
Step1: 确认wal日志级别

12.2版本wal_level日志级别为replica,如果你wal_level不是replica,需要设置为replica并重启PG。
无论使用pg_start_backup还是pg_basebackup,都设置成replica就可以了。
更多wal_level相关资料请参考笔记中的『参数设置』文章,或如下图:

Step2: 确认归档模式

如果archive_mode不是on,则需要设置为on,并重启PG。

Step3:把归档另存一份
修改postgresql.conf文件中的archive_command参数,写成如下图
archive_command = '/pg/pg12/sh/arch.sh %f %p' 

看一下脚本内容是啥

写好脚本后要赋权775权限,否则PG不能执行该脚本,报错相关可以到$PGDATA/log下查看。

修改好后reload postgresql.conf文件
pg_ctl reload

配置好后检查归档是否可以正常生成

生成的预写式文件可能看不太明白,需要转换成文件名

查看自定义归档目录中是否生成了

是生成了的

查看pg_wal目录下是否生成了

也是生成了的

Step4:设置3个参数
max_worker_processes
max_wal_senders
wal_keep_segments

max_worker_processes
系统能够支持的后台进程的最大数量,默认是8,这里建议改为16

max_wal_senders
该参数用在pg_basebackup还有备用服务器的时候,因为都是流复制的参数,这俩都用的到流复制。
那这里没有备用服务器,只是用在pg_basebackup的时候。
该参数指定来自后备服务器或流式基础备份客户端的并发连接最大数量,该值是占用的max_conections的连接数量。
默认是10,够用了,只要大于0就行,这里不改。

wal_keep_segments
pg_wal目录下所能保留的过去日志文件的最小数目。
一个wal日志是16MB,那你设置该参数为100,也就pg_wal目录需要100x16MB=1600MB的空间。如果你像本文一样设置了自定义归档路径,该路径也同样需要1600MB的空间。那如果你的数据库特别大,备份需要非常久,你就得考虑保留多少个wal日志了。
这里德哥设置的是1024,我这里设置为512。

改好后重启PG

到此前置条件都已达成。可以开始备份了。

Step5:备份
备份主要是备份数据文件目录($PGDATA)和自定义表空间目录(如果像本文这样,你的表空间放到了其他目录下)
备份可以用官方提供的工具pg_basebackup
或者
pg_start_backup结合操作系统命令tar、cpio、rsync来进行。

方法1,pg_start_backup结合cp命令来进行拷贝
连接到哪个数据库都行,主要是看用的啥用户,这里用的是postgres。
必须注意,该回话连接不能中断。否则备份退出。备份也是无效的。

now()是现在的时间是啥,把时间类型转换为text类型,因为该函数第一个参数必须是字符串。
你也可以随便写个名字,只要是能唯一标识这次备份过程的名字就行比如说gao,但是这次我们还是按照德哥的来,写成now这种

备份开始时的wal日志文件为0/2c000060,该信息需要记住。

此时会在$PGDATA下生成一个标签文件,文件名叫做backup_label(此为固定写法),包含标签名now()::text,以及执行这条指令的启动时间和wal位置,此文件当你执行pg_stop_backup的时候将消失。但是你执行pg_stop_backup之前必然已经用cp也好cpio也好等工具进行了目录的拷贝,所以你的备份里是存在backup_label文件的。
注意:恢复的时候pg会读该文件,所以该文件一定要在备份目录中存在。

start wal location这行的内容看一下知道是最开头的wal,需要记住。

接下来就可以执行操作系统cp命令备份目录了
那你备份目录需要备份两部分,$PGDATA和自定义表空间目录
第一部分

-r 复制目录及目录内所有项目
-L 总是跟随符号链接(就是你的某些文件只是软连接而已,这些软连接文件也原汁原味的保留)

可以看到这其实就只是一个目录而已。

该目录下的log目录下的内容不需要可以删除,pg_wal下的内容也不需要,可以删除(因为我们wal归档都放到别的地方了)
这里删除了log和pg_wal目录下的所有文件;当一个数据库非常大的时候,log和pg_wal下的内容是非常大的,而且我们用不到只是浪费空间。而且备份的时间还会延长
那PG默认的两个表空间pg_default在base目录下,pg_global在global目录下;咱们把$PGDATA一备份这俩表空间就都备份了,可是用户自定义的其他表空间还没备份。

第二部分
备份自定义表空间
原表空间路径为

把表空间的上级目录tablespace,备份到backup下面起个名字

那现在可以结束备份了

还在刚才的会话里执行结束语句

结束时会先结束然后切换一个wal日志以保证备份期间发生的变化都记录到了wal日志中,切换wal日志后就退出了。
同时提示你结束备份时候的wal日志叫啥名,你把它换算成文件名,就知道开始日志和结束日志是啥了。

开始日志和结束日志之间的日志都要一个不落的保留,否则无法恢复(其实如果你配置了archive_command,那你的自定义wal目录下都有这些wal归档文件)。
开始时是2A,结束时是2E,看看我们自定义wal目录下有没有

是有的。看看pg_wal目录下有没有

也是有的,像我们这样保留2份就是怎么着wal日志都丢不了。不过pg_wal的日志文件一般都不要,备份之前都删了。只留自定义归档目录下的就好。

本次备份就结束了,以后数据库的变化会存放在以后产生的wal日志中,当数据库发生损坏的时候,就可以恢复到任意时间点。

方法2:rsync备份
该备份跟方法1大部分是相同的,不同之处是把cp换成了rsync命令。那我们就再进行一次rsync备份。

1. pg_start_backup();

2. rsync备份$PGDATA和自定义表空间

-a 表示递归传输并保持文件属性
-v 显示rsync过程中详细信息
-c 传输文件完成后验证两端文件大小和最后一次修改时间是不是相同
-z 传输的时候进行压缩,减少传输的数据量
-L 遇到符号链接时,拷贝符号链接指向的目标,而不是软连接
-l  (小写的L)遇到符号链接时,拷贝符号链接本身
--eclude 排除什么文件,可以跟多个

在这里备份$PGDATA时,用了rsync的-L选项,这样pg_tblspc目录原来存的是表空间的软连接,备份出来就变成了真正的目标文件(这里是两个自定义表空间的文件gao_ts和jia_ts),所以就不用再备份自定义表空间的目录了,如下图


当恢复的时候,把pg_tblspc目录下的文件放到原来的自定义表空间目录下,然后pg_tblspc下创建软连接指向自定义表空间目录下的文件。
参考下图,就知道软连接是如何创建的了。

当然你用rsync备份的时候也可以不加-L,加-l(小写的L)就会创建符号链接本身,而不是链接的真实目标文件。这样你得再另备份自定义表空间目录。

3. pg_stop_backup();
拷贝完成后,就结束备份

方法3:pg_basebackup
该方法需要一些前置准备,请参考'pg_basebackup'文章
主要就是pg_hba.conf中写上replication

在另一台机器上备份192.168.31.123上的pg集簇
参考pg_basebackup中的实验。

德哥这里是远程备份,我这里是本机备份本机上的pg

如果你想使用本机备份本机,pg_basebackup默认会把自定义表空间放到跟你现在表空间相同路径下,叫相同名字。是的,这样就肯定提示文件会重名而导致备份失败。这里并不理解为什么pg备份要这么设置。所以我这里用了自定义表空间,就不能用-Fp的方式来备份了,需要用-Ft来让自定义表空间生成为tar.gz文件,自定义表表空间和base和pg_wal就可以都放在同一个路径下了。
Pg_basebackup备份的时候并不会备份pg_wal目录下的任意一份归档。你恢复的时候需要用restore_command拷贝到pg_wal下。
当然还原上还有一些问题,请参考笔记『pg_basebackup』

还原

背景
oracle是用rman恢复,pg是没有rman的所以pg采用了其他方法,就是靠几个参数来决定选择哪种恢复方法,恢复到什么程度。
德哥用的版本是9.6版本(猜测)那时可能只支持3种恢复方法:还原点,时间点,事务xid
而在11版本的官方档里是新增了多时间线恢复的,而我用的版本是12.2,不过12.2没有中文手册,目前中文手册最新是11版本,所以我就大部分参考11版本手册,遇到12.2新参数什么的再去看12.2版本的官档。

本次先探讨德哥的3种恢复方法,再讨论多时间线恢复。

参数介绍
这里还是引用德哥对几个参数的介绍,12.2版本新增了几个参数(多时间线参数),详细的请参考笔记『参数配置』中关于recovery的描述。

1. Recovery_target_name
指恢复到哪个还原点,前提是你还原点要先创建,才能回退到某个还原点。你可以创建很多个还原点,那恢复的时候会恢复到第一个遇到的还原点就会停止,后面的还原点不会恢复。
该功能很像oracle的guarantee point技术,也就是obar的备库闪回回退。

2. Recovery_target_time
恢复到哪个时间点,指的是wal日志中记录的recordXtime,配合recovery_target_inclusive使用
如果在同一个时间点有多个事务回滚或提交,那么recovery_target_inclusive=false则恢复到这个时间点第一个回滚或提交的事务(包含),recovery_target_inclusive=true则恢复到这个时间点最后一个回滚或提交的事务(包含)
如果时间点上刚好只有1个事务回滚或提交,那么recovery_target_inclusive=true和false作用将会一样,恢复将处理到这个事务包含的wal信息(包含)
如果时间点没有匹配的事务提交或回滚信息,那么recovery_target_inclusive=true和false作用将会一样,恢复将处理到这个时间后的下一个事务回滚或提交的wal信息(包含)

3. Recovery_target_xid
恢复到某个事务xid。配合recovery_target_inclusive使用
但是recovery_target_inclusive只影响日志输出的内容,并不影响恢复进程截止点的选择,不管recovery_target_inclusive设置成啥,恢复都截止到这个xid的wal位置(包含)。
特别需要注意的是,恢复到某个xid,xid有一个开始时分配xid和结束时提交xid;当你有一个事务很复杂,开始的时候给你分配了一个xid100,那你这个xid100需要2个小时才会结束。那此时2个小时之内有很多其他xid101和xid102也在开始和结束;所以比如说恢复到xid=100的提交点/回滚点,那xid101早就在xid100之前就提交了,xid102也在xid100提交之前就提交了;那恢复到xid100的时候,xid101和xid102也会被恢复。

4. Recovery_target_inclusive
简单的介绍上面已经说了,详细的介绍看笔记『参数配置』

还原点恢复:
我们设想的步骤是:

Session1:
drop table if exists test1;
create table test1 (id int,info text);
insert into test1 values (1,'test');
begin;
insert into test1 values (2,'test');

Session2:
select pg_create_restore_point('test01');
insert into test1 values (3,'test');
commit;
checkpoint;
select pg_walfile_name(pg_switch_wal());

按照上面的步骤,因为2,test没有提交,所以没有
3,test因为是还原点后插入的,也没有
所以正确的来说只有1,test

Session1:

Session2:

关闭数据库

删除数据目录以及表空间目录(自定义的表空间目录gao_ts和jia_ts)

还原备份以及表空间软连接
我这里用的是pg_basebackup备份的东西,就是下面这条命令

先进入到这个备份下,把base还原到$PGDATA;这样目录就都出来了,而且pg_default和pg_global表空间也出来了。

关于自定义表空间的恢复
Pg11(包含)之前:
接下来就是把自定义表空间恢复出来,先看看表空间和OID都是怎么一一对应的,这个tablespace_map是备份的时候生成的表空间实际对应的映射文件,你手工创建表空间软连接的时候不至于啥也不知道。

恢复自定义表空间
进入到备份下,通过上图我们知道16470是gao_ts表空间,所以就恢复到这里
前提是你的/pg/pg12/tablespace/gao_ts这个路径是存在且有权限的,其他表空间也一样

这个16470恢复出来是这样子的

所以你需要把表空间的名字的目录(gao_ts)创建出来。tar命令可不会为你创建目录。
同样把16471恢复到jia_ts下

然后到$PGDATA下的pg_tblspc创建自定义表空间的软连接
依据tablespace_map文件

Pg11(不包含)之后,比如说我现在用的12.2版本
你只需要把自定义表空间真实的路径创建出来,然后从备份里恢复表空间的目录到相应的位置(参考tablespace_map文件)就可以了。
不再需要到恢复的$PGDATA/pg_tblspc下创建软连接文件了,保持pg_tblspc目录是空目录就可以了。
pg在恢复的时候自动读取tablespace_map文件然后在pg_tblspc下创建软连接。
当然你手工在pg_tblspc下创建软连接,像上面11版本之前那样操作也是可以的,pg会自动覆盖掉你的软连接文件,所以也无所谓。

如果你备份的时候用的是文件系统的备份比如说本例中的rsync,那你排除了两个目录pg_wal和log;要把这两个目录创建出来。
而我这里用的备份是pg_basebackup,这个命令不给你排除任意文件,所以我这里到不用创建这两个目录。
如果对pg_basebackup命令有疑问可以参考笔记『pg_basebackup』

接下来就要配置恢复用的参数

Postgresql 11 之前:
在pg11版本(包含)之前一直用的是recovery.conf文件,该文件存放于$PGHOME/share目录下,对于本环境来说路径是

需要先把示例文件recovery.conf.sample拷贝出来放到$PGDATA下,修改参数,pg_ctl start的时候才会读取到该文件。
用完该文件后pg会把该文件更名为done后缀,以免你下次重启pg又开始自动回复了。
当然你恢复的时候要防止普通用户连接,直到你确定恢复成功了。可以把pg_hba.conf相关的配置注释掉。

Postgresql 11(不包含)之后:
这里使用的版本是12.2
recovery.conf文件废弃不用了,改为了修改postgresql.conf文件。并且你要手工在$PGDATA下面touch创建recovery.signal文件,该文件里啥也不写保持一个空文件;
这样搞可能是为了更方便管理吧。

还原点只需要用到3个参数

restore_command
你的备份里是只有备份期间产生的pg_wal日志的,没有之前和之后的日志,所以你需要把archive_command拷贝走的日志再拷贝回来。pg才能认到。那怎么拷贝回来就用这个参数。比如说

%f 表示wal文件名是啥
%p 表示带绝对路径的wal文件命
照上面这么写,恢复的时候pg会自动需要哪一个%f就拷贝哪一个%f到%p

当然你也可以写一个shell脚本带参数,比如说德哥的

创建完脚本一定要赋予775权限或者其他权限,否则pg无法执行该脚本。

本例就不使用德哥的了,我就用上图中的简单cp命令

recovery_target_name
恢复到哪个还原点
直接写还原点名字就行,比如说我这里还原点就叫test01;

recovery_target_timeline
恢复到哪个时间线里,你需要指定时间线ID才行;这里指定latest,将会恢复到该归档中能找到的最新的时间线,比如说你有1,2,3,3个时间线,那最新的就是3时间线。
这里就直接指定latest

多提一下因为recovery_target_name是不受recovery_target_inclusive影响的,所以不指定该参数。

recovery_target_action
德哥并没有设置该参数是啥,该参数默认值是pause,表示恢复到还原点后将会暂停。在暂停后不会对数据库进行进一步的恢复,暂停状态可以无限制的继续下去而不出问题。此时是可以对pg建立连接并访问的,用起来跟正常的pg是一样的。
但是我感觉还是不暂停的好。如果暂停后需要关闭这次恢复,则需要执行函数pg_wal_replay_resume();该函数的意思是如果恢复被暂停,则恢复完成让恢复正常退出。
如果恢复到这个时间点不是你想要的数据,你可以关闭pg,更改恢复的还原点名字或者改用其他方法恢复比如说时间点,然后重启pg尝试再次恢复。
这里我就不用pause参数了,用promote;表示恢复处理将会结束并且服务器开始接受新连接。
还有一个参数是shutdown,意思是达到恢复目标之后停止服务器,当然这里也不用shutdown;

德哥用了3个参数我用了4个,现在保存退出postgresql.conf文件

在$PGDATA下创建recovery.signal文件,里面啥也不用些,搞个空文件就行了

该文件是干啥的不清楚(12.2的官方文档上没说),反正pg恢复完成后会删除recovery.signal,以防止你下一次恢复意外进入恢复模式。我这恢复非常快,也无法查看恢复过程中该文件中写了啥内容,以后再说吧。

然后启动pg_ctl start

日志提示

第一个红框,是在你完成任意一次恢复(无论何种方法恢复)后,会创建的时间线历史文件,(具体的看笔记『参数配置』会有详细的介绍)我这里是回退到某个还原点。
因为pg在一次恢复完成后才会创建时间线历史文件,所以第一次恢复说找不到时间线历史文件是正常的。
恢复完成后查看时间线历史文件中是已经生成了的,就是下面这个00000002.history。

时间线ID是wal文件名子的一部分,所以多时间线wal归档文件名不会重复。
上图中的1就是时间线ID;它的归档都是带1的,比如说下图这样的

当发生了一次任何方式的恢复,时间线发生了变动,变为了2;归档命名也就改变了,成了00000002000000啥的

那你以后生成的归档都将会是2时间线ID的了。如果想回退到时间线1的时候,需要指定recovery_target_timeline=1

第二个红框,开始恢复到某个还原点了。

注意:
恢复完成后文件名字有了变化

像backup_label和tablespace_map文件都自动加上了old后缀,表示我已经用完了这个文件,你可以删除或者继续留着了。

验证

是符合我们预期的。

按时间点恢复

如果恢复到第一个时间点,则只有1,test数据
如果恢复到第二个时间点,则还是只有1,test数据

关闭pg
删除$PGDATA,注意这里并没有删除自定义表空间目录
使用备份恢复$PGDATA,此时pg_tblspc下是没有软连接文件的
如果你的log和pg_wal目录不存在则创建,这两个目录里面如果有内容需要都删掉。

如果你不删除$PGDATA,那你的目录下是没有backup_label和tablespace_map文件的,你关闭pg,启动pg,pg不认为你是想恢复。即使你改了postgresql.conf参数文件。

配置postgresql.conf文件
需要更改的配置

这里的时间跟datagrip查出来的不太一样,datagrip查出来的应该是完全体,这里用的是简写

更改该参数需要重启PG,老版本该参数是true和false,12.2版本是on和off,不过你写true和false也可以认得出来;
这里德哥用的是false参数,false的意思是仅在恢复目标之前停止。
true是仅在指定的恢复目标之后停止,默认是true。
这里我们选择on(true),而不是德哥用的false,试试看
上面的按还原点恢复是不受该参数的影响的,所以没有启用这个参数,但是时间点恢复是跟该参数有关联的所以要给个值。

latest就是找到最新的时间线来恢复。
比如说有1,2,3时间线,那latest就会找3这个最新的时间线的归档来恢复。

promote就是恢复到指定点就退出恢复并允许新连接。

那按时间点恢复就多了一个参数recovery_target_inclusive=on;

在$PGDATA下创建recovery_signal文件

启动pg

日志里报:

同样有几个疑点
第一个报错说找不到3时间线的历史文件,可是我只有2时间线的历史文件,3的话要这次恢复完才会生成。
第二个报错说找不到2时间线的40日志,可是我最早的2时间线日志是42;不存在40日志
第三个报错41同理
第四个报错跟第一个报错是一样的

现在归档目录文件如下

验证:

符合我们的预期

恢复虽然成功,但是日志中有LOG提示,虽然这不算是报错,但心里看到什么目录文件不存在还是不舒服。
时间点恢复到此完成。

按事务xid恢复
Session1:

此时会话1中的xid最新为667;

Session2:

Session2 xid最新为670

回到session1:
因为session1没有提交,如下图红框 当前事务xid还是667

然后继续操作

上面是德哥的例子,我也没搞明白为啥搞这么复杂,直接回到某个xid点不好么
德哥可能是想展示恢复到xid开始分配和结束之间的xid怎么办。理论上是会按照语句实际是commit了还是rollback来执行。

现在我们把数据库恢复到事务xid 668上。理论上是有668 test的,大于668的都不会存在,之前2个667的记录也会回退也不存在;那正常来说也就会存在1,test和668,test;

checkpoint;
Select pg_switch_wal();

关闭数据库
删除$PGDATA
还原$PGDATA
这些步骤跟上面的步骤一样,这里只说不一样的

修改postgresql.conf参数文件

启动pg

日志:
同样报了很多找不到归档的错误

验证

符合我们预期

恢复到lsn
略,以后再补充吧

自动备份
https://yq.aliyun.com/articles/59359可以借鉴德哥的
image.png

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章