事务日志是数据库的重要组成部分,因为即使数据库发生系统故障,也要求所有数据库管理系统不能丢失任何数据。它是数据库系统中所有更改和操作的历史记录日志,以确保没有数据因故障而丢失,例如电源故障或导致服务器崩溃的其他服务器故障。由于日志包含有关已执行的每个事务充足的信息,因此数据库服务器应能够通过在服务器崩溃时重放事务日志中的更改和操作来恢复数据库集群。
在计算机科学领域,WAL是Write Ahead Logging的首字母缩写,它是将数据改动和行为写入事务日志的协议或规则,而在PostgreSQL中,WAL是Write Ahead Log的首字母缩写。该术语是事务日志的同义词,用于表示将事务写入事务日志(WAL)相关的实现机制。虽然这有点难理解,但在本文中采用了Postgresql的定义。
WAL机制最初在7.1版中实现,以减轻服务器崩溃的影响。时间点恢复(PITR)和流复制(SR)也是建议此机制之上的,这两种情况分别在第10章和第11章中描述。
尽管对WAL机制的理解对于使用和管理PostgreSQL至关重要,但是因为这种机制的复杂性使得无法简单的概括和描述。所以PostgreSQL中WAL的完整解释如下。在第一部分中,提供了WAL的整体认知相关图片,介绍了一些重要的概念和关键字。在后续部分中,描述了以下话题:
我们来看看WAL机制的概述。为了澄清WAL一直在研究的问题,第一小节说明了当PostgreSQL没有实现WAL时发生崩溃时会发生什么。第二小节介绍了一些关键概念,并展示了本章主要主题的概述,WAL数据的编写和数据库恢复处理。最后一小节完成了WAL的概述,增加了一个关键概念。
在本节中,为了简化描述,使用了仅包含一个页面的表TABLE_A。
如第8章所述,为了提升对关系页面的访问效率,每个DBMS都使用了共享缓冲池。
假设我们在PostgreSQL上的TABLE_A中插入一些数据,这些数据没有使用WAL功能;这种情况如图9.1所示。
图9.1 没有WAL的插入操作
(1)发出第一个INSERT语句,PostgreSQL将TABLE_A的页面从数据库加载到共享缓冲池中,并将一个元组插入到页面中。此页面不会立即写入数据库。 (如第8章所述,修改后的页面通常称为脏页面。)
(2)发出第二个INSERT语句,PostgreSQL在缓冲池的页面中插入一个新的元组。此页面也不会写入持久化存储。
(3)如果操作系统或PostgreSQL服务器因电源故障等原因而失败,则所有插入的数据都将丢失。
因此在系统故障的时候,没有WAL的数据库系统是很脆弱的。
为了在不影响性能的情况下处理上述系统故障,PostgreSQL支持WAL。在本小节中,描述了一些关键字和关键概念,然后描述了WAL数据的写入和数据库的恢复。
PostgreSQL将所有修改作为历史数据写入持久存储,以准备故障的时候使用。在PostgreSQL中,历史数据称为XLOG记录或WAL数据。
XLOG记录通过更改操作(如插入,删除或提交操作)写入内存中的WAL缓冲区。当事务提交/中止时,它们立即把WAL段文件写入到磁盘上。 (严格地说,在其他情况下也可能会写入XLOG记录。详细信息将在第9.5节中描述。)XLOG记录的LSN(日志序列号)表示其记录在事务日志中写入的位置。LSN号作为XLOG记录唯一ID。
顺便说一句,当我们考虑数据库系统如何恢复时,可能会有一个问题; PostgreSQL从什么时候开始恢复?答案是REDO point;也就是说,REDO point是在最新的检查点启动时写入XLOG记录的位置(PostgreSQL中的检查点在第9.7节中描述)。事实上,数据库恢复处理与检查点处理密切相关,这两种处理都是不可分割的。
由于主要关键词和概念的介绍刚刚完成,从现在开始将用WAL描述元组插入。参见图9.2和以下描述。 (另请参阅此幻灯片。)
图9.2 有WAL的插入操作
'TABLE_A的LSN’是表TABLE_A的页头中显示pd_lsn的值。 'page的LSN’是一样的。
(1)检查点的后台进程,定期执行检查点。每当checkpointer启动时,它会将名为checkpoint record的XLOG记录写入当前WAL段。此记录包含最新REDO点的位置。
(2)发出第一个INSERT语句,PostgreSQL将TABLE_A的页面加载到共享缓冲池中,将一个元组插入页面,创建该语句的XLOG记录并将其写入LSN_1位置的WAL缓冲区,并且更新TABLE_A的LSN从LSN_0到LSN_1。
在此示例中,此XLOG记录是头数据和整个元组。
(3)当此事务提交时,PostgreSQL创建并将此提交操作的XLOG记录写入WAL缓冲区,然后将WAL缓冲区中的所有XLOG记录刷新到LSN_1的WAL段文件中。
(4)发出第二个INSERT语句,PostgreSQL在页面中插入一个新元组,创建该元组的XLOG记录并将其写入LSN_2的WAL缓冲区,并将TABLE_A的LSN从LSN_1更新为LSN_2。
(5)当该语句的事务提交时,PostgreSQL以与步骤(3)相同的方式操作。
(6)想象一下操作系统何时发生故障。即使共享缓冲池中的所有数据都丢失,页面的所有修改都已作为历史数据写入WAL段文件。
以下将说明如何将数据库集群恢复到崩溃之前的状态。没有必要做任何特殊的事情,因为PostgreSQL会通过重启自动进入恢复模式。见图9.3(和此幻灯片)。 PostgreSQL按顺序将从REDO点读取和重放相应WAL段文件中的XLOG记录。
图9.3 使用WAL做数据恢复
(1)PostgreSQL从相应的WAL段文件中读取第一个INSERT语句的XLOG记录,将TABLE_A的页面从数据库集群加载到共享缓冲池中。
(2)在尝试重放XLOG记录之前,PostgreSQL将XLOG记录的LSN与相应页面的LSN进行比较,这样做的原因将在第9.8节中描述。重放XLOG记录的规则如下所示。
如果XLOG记录的LSN大于页面的LSN,则XLOG记录的数据部分将插入页面,页面的LSN将更新为XLOG记录的LSN。另一方面,如果XLOG记录的LSN较小,除了读取下一个WAL数据之外没有其他任何操作。
在此示例中,XLOG记录被重放,因为XLOG记录的LSN(LSN_1)大于TABLE_A的LSN(LSN_0);然后,TABLE_A的LSN从LSN_0更新为LSN_1。
(3)PostgreSQL以相同的方式重放剩余的XLOG记录。
PostgreSQL可以通过以时间顺序重放在WAL段文件中写入的XLOG记录来以这种方式恢复自身。因此,PostgreSQL的XLOG记录显然是REDO日志。
虽然写XLOG日志会花费一定的代价,但与写整个修改过的页面相比就不算什么了。我们相信,我们可以获得了更大的好处,比如系统故障的容忍度。
假设因为后台进程写入脏页时由于操作系统原因而失败,TABLE_A的存储的页面数据损坏。由于XLOG记录无法在损坏的页面上重播,因此我们需要一个额外附加的功能。
PostgreSQL支持一种称为整页写入的功能来处理此类故障。如果启用,PostgreSQL会在每个检查点之后,将每个页面第一次更改期间写头数据和整个页面作为XLOG记录;默认已启用。在PostgreSQL中,包含整个页面的这种XLOG记录称为备份块(或full-page image)。
在启用了整页写入的情况下,让我们再次描述元组的插入。参见图9.4和以下描述。
图9.4 整页写
(1)检查点启动一个检查点进程。
(2)在第一个INSERT语句插入时,虽然PostgreSQL和前一个小节中的操作几乎相同,但此XLOG记录的是整个页面的备份块(即它包含整个页面),因为这是在最新的检查点之后首次写这个页面。
(3)当此事务提交时,PostgreSQL的操作方式与前一小节相同。
(4)在第二个INSERT语句插入时,PostgreSQL的操作方式与上一小节相同,因为此XLOG记录不是备份块(不包含整个页面)。
(5)当该语句的事务提交时,PostgreSQL的操作方式与前一小节相同。
(6)为了演示整页写入的有效性,这里我们考虑这样的情况:当后台写入进程将其写入HDD时发生操作系统故障,磁盘上TABLE_A的页面已被破坏。
重新启动PostgreSQL服务以修复损坏的页面。参见图9.5和以下描述。
图9.5. 使用备份块恢复数据库
(1)PostgreSQL读取第一个INSERT语句的XLOG记录,并将损坏的TABLE_A页面从数据库集群加载到共享缓冲池中。在此示例中,XLOG记录是备份块,因为根据整页写入的写入规则,每个页面的第一个XLOG记录始终是备份块。
(2)当XLOG记录是备份块时,则会使用另一个重放规则:记录的数据部分(即页面本身)将被覆盖到页面上,而不管两个LSN的值如何,并且页面的LSN更新到XLOG记录的LSN。
在此示例中,PostgreSQL将记录的数据部分覆盖到损坏的页面,并将TABLE_A的LSN更新为LSN_1。通过这种方式,损坏的页面由其备份块恢复。
(3)由于第二个XLOG记录是非备份块,因此PostgreSQL的操作方式与前一小节中的操作方式相同。
即使发生了一些数据写入失败,也可以恢复PostgreSQL。 (当然,如果发生文件系统或介质故障,则不适用。)
从逻辑上讲,PostgreSQL将XLOG记录写入事务日志,是一个8字节长的虚拟文件(16 ExaByte)。
由于产生事务日志实际上是无限的,因此可以说8字节地址空间足够大,我们不可能处理容量为8字节长度的文件。因此,在PostgreSQL中的事务日志默认切分为16 MB的文件,每个文件称为WAL段。见图9.6。
WAL文件大小
在版本11以后,当通过initdb命令创建PostgreSQL集群时,可以使用–wal-segsize选项配置WAL段文件的大小。
图9.6 事物日志和WAL文件
WAL文件名为十六进制24位数字,命名规则如下:
timelineId
PostgreSQL的WAL包含timelineId(4字节无符号整数)的概念,将在第10章时间点恢复(PITR)中详细介绍。所以,本章中timelineId固定为0x00000001,因为这个概念在以下的介绍说明中不是必需的。
第一个WAL段文件是000000010000000000000001.如果第一个已经写满了XLOG记录,则提供第二个000000010000000000000002。后续文件连续按升序使用,直到填写0000000100000000000000FF之后,才提供下一个000000010000000100000000。这样,每当最后两位数字结转时,中间的8位数字就会增加一位。
同样,在0000000100000001000000FF填满后,将会写入000000010000000200000000,依此类推。
pg_xlogfile_name / pg_walfile_name
使用内置函数pg_xlogfile_name(9.6版本及之前)或pg_walfile_name(10版本及之后),我们可以找到包含指定LSN的WAL文件名。一个例子如下所示:
默认情况下,WAL段是一个16 MB的文件,内部切分为8192字节(8 KB)的页面。第一页是由结构体XLogLongPageHeaderData定义的头数据,而所有其他页的标题具有由结构体XLogPageHeaderData定义的页面信息。在页头之后,XLOG记录从头开始按降序写入每个页面。见图9.7。
图9.7 WAL文件的内部结构
XLogLongPageHeaderData结构和XLogPageHeaderData结构在src/include/access/xlog_internal.h中定义。这里省略了对这两种结构的说明,因为在以下描述中不需要这些结构。
XLogLongPageHeaderData的定义
XLogPageHeaderData的定义
XLOG记录包括头部以及相关的数据部分。第一小节描述了头部结构;其余两个小节分别解释版本9.4或更早版本以及版本9.5中的数据部分的结构。 (版本9.5中的数据格式已更改。)
所有XLOG记录都有一个由结构XLogRecord定义的通用头部分。这里,9.4版本以及之前的结构如下所示,但是它在9.5版中已更改。
除了两个变量之外,大多数变量都很好理解,不需要描述。
xl_rmid和xl_info都是与资源管理器相关的变量,资源管理器是与WAL功能相关的操作的集合,例如写入和重放XLOG记录。每个PostgreSQL版本的资源管理器数量往往会增加,10版本包含以下内容:
以下是资源管理器如何在各方面工作的一些代表性示例:
在9.5或更高版本中,已从XLogRecord中移除了一个变量(xl_len)以优化XLOG记录格式,从而减少了几个字节的大小。
版本9.4或更早版本中的XLogRecord结构在src/include/access/xlog.h中定义,版本9.5或更高版本中的XLogRecord结构在src/include/access/xlogrecord.h中定义。
heap_xlog_insert和heap_xlog_update在src/backend/access/heap/heapam.c中定义;函数xact_redo_commit在src/backend/access/transam/xact.c中定义。
XLOG记录的数据部分分为备份块(整页)或非备份块(操作的不同数据)。
图9.8 XLOG记录实例(9.4版本以及更早的版本)
下面使用一些具体示例描述XLOG记录的内部布局。
备份块如图9.8(a)所示。它由两个数据结构和一个数据对象组成,如下所示:
BkpBlock包含用于在数据库集群中标识此页面的变量(即relfilenode和包含此页面的关系的fork编号,以及此页面的块编号),以及此页面的可用空间的起始位置和长度。
BkpBlock定义如下:
在非备份块中,数据部分的布局根据每个操作而不同。这里,INSERT语句的XLOG记录作为代表性示例来解释。见图9.8(b)。在这种情况下,INSERT语句的XLOG记录由两个数据结构和一个数据对象组成,如下所示:
结构xl_heap_insert包含在数据库集群中标识插入的元组的变量(即包含此元组的表的relfilenode,以及此元组的tid),以及此元组的可见性标志。
从结构体xl_heap_header的源代码注释中描述了从插入的元组中删除几个字节的原因:
在WAL中我们不会存储插入或更新元组的整个固定的部分(HeapTupleHeaderData);我们可以通过重建字段来节省一些字节,这些字节可以在WAL中的其他地方找到,或者不需要重建。
这里展示的另一个例子。见图9.8(c)。检查点的XLOG记录非常简单;它由两个数据结构组成,如下所示:
xl_heap_header结构在src/include/access/htup.h中定义,而CheckPoint结构在src/include/catalog/pg_control.h中定义。
在9.4或更早版本中,没有XLOG记录的通用格式,因此每个资源管理器必须定义一个自己的格式。在这种情况下,维护源代码和实现与WAL相关的新功能变得越来越困难。为了解决这个问题,9.5版本中引入了一种不依赖于资源管理器的通用的结构化格式。
XLOG记录的数据部分可以分为两部分:头部分和数据部分。见图9.9。
头部分包含零个或多个XLogRecordBlockHeaders和零个或一个XLogRecordDataHeaderShort(或XLogRecordDataHeaderLong);但必须至少包含其中一个。当记录存储整页(即备份块)时,XLogRecordBlockHeader包括XLogRecordBlockImageHeader,并且如果其块被压缩,则还包括XLogRecordBlockCompressHeader。
数据部分由零个或多个块数据和零个或一个主数据组成,它们分别对应于XLogRecordBlockHeader和XLogRecordDataHeader。
WAL压缩
在9.5或更高版本中,通过设置参数wal_compression = enable,可以使用LZ压缩方法压缩XLOG记录中的整个页。在这种情况下,将添加结构体XLogRecordBlockCompressHeader。
该特征有两个优点和一个缺点。优点是降低了写入记录的I / O成本并抑制了WAL段文件的消耗。缺点是需要消耗大量CPU资源才能进行压缩。
XLogRecordBlockHeaders定义如下:
XLogRecordDataHeaderShort定义如下:
XLogRecordBlockImageHeader定义如下:
XLogRecordBlockCompressHeader定义如下:
图9.10 XLOG记录实例(9.5版本以及以后的版本)
INSERT语句创建的备份块如图9.10(a)所示。它由四个数据结构体和一个数据对象组成,如下所示:
XLogRecordBlockHeader包含用于标识数据库集群中的块的变量(relfilenode,fork编号和块编号); XLogRecordImageHeader包含此块的长度和偏移号。 (这两个头结构一起存储的数据和在版本9.4之前使用的BkpBlock的数据相同。)
XLogRecordDataHeaderShort存储xl_heap_insert结构的长度,该结构是记录的主要数据。 (见下文。)
除了在某些特殊情况下(例如,在逻辑解码和推测性插入中),一般不使用包含整页图像的XLOG记录的主要数据。当这个记录重放时会忽略它,这是冗余数据。它可能在未来得到改善。
此外,备份块记录的主要数据取决于创建它们的语句。例如,UPDATE语句附加xl_heap_lock或xl_heap_updated。
接下来,INSERT语句创建的非备份块记录将描述如下(也参见图9.10(b))。它由四个数据结构和一个数据对象组成,如下所示:
XLogRecordBlockHeader包含三个值(relfilenode,fork编号和块编号),用于指定插入元组的块,以及插入元组的数据部分的长度。 XLogRecordDataHeaderShort包含新xl_heap_insert结构的长度,该结构是此记录的主要数据。
新的xl_heap_insert只包含两个值:块内该元组的偏移量,以及可见性标志;它变得非常简单,因为XLogRecordBlockHeader存储了旧数据中包含的大部分数据。
最后一个例子,检查点记录如图9.10(c)所示。它由三个数据结构组成,如下所示:
结构xl_heap_header在src/include/access/htup.h中定义,CheckPoint结构在src/include/catalog/pg_control.h中定义。
尽管新格式对我们来说有点复杂,但它是为资源管理器的解析器设计的,并且XLOG记录的许多类型的尺寸通常小于老的结构定义。主结构的尺寸如图9.8和9.10所示。所以你可以计算这些记录的大小并相互比较。 (新检查点的大小大于老版本的,但它包含更多变量。)
我们完成了热身练习,现在我们已经准备好了解XLOG记录的写入。我将在本节中尽可能准确地解释它。
首先,执行以下语句来探索PostgreSQL内部:
testdb=# INSERT INTO tbl VALUES ('A');
通过发出上述语句,调用内部函数exec_simple_query(); exec_simple_query()的伪代码如下所示:
在以下段落中,将解释伪代码的每一行以理解XLOG记录的写入;另见图 9.11和9.12。
(1)函数ExtendCLOG()将此事务的状态“IN_PROGRESS”写入(内存中)CLOG中。
(2)函数heap_insert()将堆元组插入共享缓冲池的目标页面,创建该页面的XLOG记录,并调用函数XLogInsert()。
(3)函数XLogInsert()将heap_insert()创建的XLOG记录写入LSN_1的WAL缓冲区,然后将修改后的页面的pd_lsn从LSN_0更新为LSN_1。
(4)调用函数finish_xact_command()提交事务,并创建此提交操作的XLOG记录,然后函数XLogInsert()将此记录写入LSN_2的WAL缓冲区。
上图XLOG记录的格式是9.4版本的
(5)函数XLogWrite()将WAL缓冲区上的所有XLOG记录刷新到WAL段文件。
如果参数wal_sync_method设置为“open_sync”或“open_datasync”,则同步写入记录,因为会使用指定标志O_SYNC或O_DSYNC的open()函数通过系统调用写入所有记录。如果参数设置为’fsync’,‘fsync_writethrough’或’fdatasync’,则系统将调用相应的函数 - 带有F_FULLFSYNC选项的fsync(),fcntl()或fdatasync()。所以在任何情况下,都会确保将所有XLOG记录写入存储。
(6)函数TransactionIdCommitTree()将此事务的状态从“IN_PROGRESS”更改为“COMMITTED”。
图9.12 接图9.11 XLOG记录的写入顺序
在上面的示例中,commit动作导致将XLOG记录写入WAL段,但是当发生以下任何一种情况时,会发生写入操作:
如果出现上述情况之一,WAL缓冲区上的所有WAL记录都将写入WAL段文件,无论它们的事务是否已提交。
当然,DML(数据操作语言)操作会写XLOG记录,但非DML操作也是如此。如上所述,提交操作会写入包含已提交事务的id的XLOG记录。另一个例子是检查点会将该检查点的一般信息写入到XLOG记录。此外,SELECT语句在特殊情况下也会创建XLOG记录,但一般情况下不会创建XLOG。例如,如果在SELECT语句处理期间删除了不必要的元组,而且在页面中发生了由HOT(Heap Only Tuple)引发的碎片整理,则修改页面的XLOG记录将写入WAL缓冲区。
WAL写进程是一个后台进程,用于定期检查WAL缓冲区并将所有未写入的XLOG记录写入WAL段。此过程的目的是避免爆发性的写XLOG记录。如果此进程没有启用,则在提交大量数据时,写入XLOG记录可能会遇到瓶颈。
WAL写进程默认工作的,并且无法禁用。检查间隔设置为配置参数wal_writer_delay,默认值为200毫秒。
在PostgreSQL中,checkpointer(后台)进程执行检查点;当下列之一发生时,其进程开始:
当超级用户手动发出CHECKPOINT命令时,也会执行检查点。
在版本9.1或更早版本中,如第8.6章节所述,后台写器进程同时执行checkpointing和脏页写。
在以下章节中,将描述检查点的概述和保存当前检查点的元数据的pg_control文件。
检查点进程有两个方面:为数据库恢复做准备和共享缓冲池上的脏页清除。在本小节中,将着重于前一个处理来描述其内部处理。参见图9.13和以下描述。
图9.13 Postgresql 检查点的内部处理过程
(1)检查点开始后,REDO点存储在内存中; REDO点是在最新检查点启动时写入XLOG记录的位置,并且是数据库恢复的起点。
(2)将此检查点的XLOG记录(即检查点记录)写入WAL缓冲区。记录的数据部分由结构CheckPoint定义,结构包含几个变量,例如存储在步骤(1)中的REDO点。
此外,写入检查点记录的位置实际上称为检查点。
(3)共享缓冲池中的所有数据(例如,CLOG的内容等)被刷新到磁盘。
(4)共享缓冲池中的所有脏页都会逐渐写入并刷新到存磁盘。
(5)更新pg_control文件。此文件包含一些基本信息,例如检查点记录所写的位置(a.k.a.检查点位置)。稍后详细描述此文件。
为了从数据库恢复的角度总结上述描述,检查点创建包含REDO点的检查点记录,并将检查点位置和更多内容存储到pg_control文件中。因此,PostgreSQL可以通过从pg_control文件提供的REDO点(从检查点记录获得)重放WAL数据来恢复自身。
由于pg_control文件包含检查点的基本信息,因此它对于数据库恢复肯定是必不可少的。如果它被破坏或不可读,则恢复过程无法启动以便无法获得起点。
即使pg_control文件存储了40多个项目,但是下一节中仅需要三个项目,如下所示:
pg_control文件存储在global子目录中;可以使用pg_controldata实用程序显示其内容。
PostgreSQL 11中将会删除先前检查点
PostgreSQL 11或更高版本只存储包含最新或更新的检查点的WAL段;将不会存储包含先前检查点的旧的段文件,这样做为了以减少用于在pg_xlog(pg_wal)子目录下保存WAL段文件的磁盘空间。详细了解此主题。
PostgreSQL实现了基于重做日志的恢复功能。如果数据库服务器崩溃,PostgreSQL通过从REDO点顺序重放WAL段文件中的XLOG记录来恢复数据库集群。
在本节之前我们已经多次讨论了数据库恢复,因此我将描述有关恢复的两个方面,这些内容尚未解释。
第一点是PostgreSQL如何开始恢复处理。当PostgreSQL启动时,它首先读取pg_control文件。以下是从那个时间点开始恢复处理的细节。参见图9.14和以下描述。
9.14 恢复处理描述
(1)PostgreSQL在启动时读取pg_control文件的信息。如果状态项处于’in production’,PostgreSQL将进入恢复模式,因为这意味着数据库没有正常停止;如果’shutdown’,它将进入正常的启动模式。
(2)PostgreSQL从相应的WAL段文件中读取最新的检查点记录,该记录位于pg_control文件中,并从记录中获取REDO点。如果最新的检查点记录无效,PostgreSQL将读取前一个检查点的记录。如果两个记录都没有读取到,将自行放弃恢复。 (请注意,先前的检查点在从PostgreSQL 11中不会再存储。)
(3)适当的资源管理器从REDO点开始按顺序读取和重放XLOG记录,直到它们到达最新WAL段的最后一个点。当重放XLOG记录并且它是备份块时,无论其LSN如何,它都将覆盖相应表的页面。否则,仅当此记录的LSN大于相应页面的pd_lsn时,才会重放(非备份块)XLOG记录。
第二点是关于LSN的比较:为什么应该比较非备份块的LSN和相应页面的pd_lsn。与前面的示例不同,这里将使用强调两个LSN之间进行比较的特定示例来解释。见图。 9.15和9.16。 (注意,省略了WAL缓冲区以简化描述。)
(1)PostgreSQL将一个元组插入TABLE_A,并在LSN_1处写入一条XLOG记录。
(2)后台写进程将TABLE_A的页面写入磁盘。此时,此页面的pd_lsn为LSN_1。
(3)PostgreSQL在TABLE_A中插入一个新元组,并在LSN_2处写入一条XLOG记录。修改后的页面还没有写入磁盘。
与概述中的示例不同,在这种情况下,TABLE_A的页面已写入磁盘。
使用immediate模式关闭,然后启动。
图9.16 数据库恢复
(1)PostgreSQL加载第一个XLOG记录和TABLE_A的页面,但不重放它,因为该记录的LSN不大于TABLE_A的LSN(两个值都是LSN_1)。
(2)接下来,PostgreSQL重放第二个XLOG记录,因为该记录的LSN(LSN_2)大于当前TABLE_A的LSN(LSN_1)。
从此示例中可以看出,如果非备份块的重放顺序不正确或者多次重放非备份块,则数据库集群将不一致。简而言之,非备份块的重做(重放)操作不是幂等的。因此,为了保留正确的重放顺序,当且仅当其LSN大于相应页面的pd_lsn时,才重放非备份块记录。
另一方面,由于备份块的重做操作是幂等的,因此备份块可以重放任意次,而不管其LSN如何。
PostgreSQL将XLOG记录写入存储在pg_xlog子目录(版本10或更高版本,pg_wal子目录)中的一个WAL段文件中,如果已填满旧文件,则切换为新文件。 WAL文件的数量根据服务端几个参数的配置而有所不同。此外,他们的管理策略在9.5版本中得到了改进。
在以下小节中,描述了WAL段文件的切换和管理。
发生以下任一情况时,会发生WAL段切换:
切换后的段文件通常被回收(重命名和重用)以供将来使用,如果没有必要,也会删除掉。
每当检查点启动时,PostgreSQL都会预估和准备下一个检查点周期所需的WAL段文件数。这种预估是参考前一个检查点周期中消耗的文件数量。它们从前一个检点点包含REDO点的段做统计,并且该值在min_wal_size(默认情况下,80 MB,即5个文件)和max_wal_size(1 GB,即64个文件)之间。如果检查点启动,将保留或回收必要的文件,同时删除不必要的文件。
具体例子如图9.17所示。假设在检查点启动之前有六个文件,WAL_3包含先前的REDO点(在版本10或更早版本中是先前的REDO点;在版本11或更高版本中,是最新REDO点),并且PostgreSQL预估需要五个文件。在这种情况下,WAL_1将重命名为WAL_7以进行回收,并且将删除WAL_2。
比前一个REDO点更老的的文件会被删除,因为从第9.8节中描述的恢复机制可以清楚地看出,它们永远不会被使用。
图9.17 检查点回收和删除WAL段文件
如果由于WAL活动激增而需要更多文件,则在WAL文件的总大小小于max_wal_size时将创建新文件。例如,在图9.18中,如果已填充WAL_7,则新创建WAL_8。
图9.18 创建WAL段文件
WAL文件的数量会根据数据库的繁忙程度自适应地改变。如果WAL数据写入的数量不断增加,则WAL段文件的预估数量以及WAL文件的总大小也逐渐增加。相反的情况下(即WAL数据写入量减少),这些减少。
如果WAL文件的总大小超过max_wal_size,则将启动检查点。图9.19说明了这种情况。通过检查点,将创建一个新的REDO点,之前最新的REDO点变为了前一个REDO点;然后将回收不必要的旧文件。通过这种方式,PostgreSQL将始终只保存数据库恢复所需的WAL段文件。
图9.19 检查并回收WAL段文件
配置参数wal_keep_segments和复制槽功能也会影响WAL段文件的数量。
WAL段文件的数量主要由以下三个参数控制:checkpoint_segments,checkpoint_completion_target和wal_keep_segments。它的数量通常大于(
(2+checkpoint_completion_target)×checkpoint_segments+1)或
(checkpoint_segments+wal_keep_segments+1),也可以因数据库的繁忙程度暂时达到(3×checkpoint_segments+1),复制槽也会影响他们的数量。
如第9.7节所述,检查点进程会在消耗了checkpoint_segments文件的数量时发生。因此,为了保证WAL文件中始终包含两个或更多REDO点,所以文件的数量始终大于2×CHECKPOINT_SEGMENTS。如果发生超时,情况也是如此。因此,PostgreSQL始终保存足够的WAL段文件(有时超过必要数量)以进行恢复。
在版本9.4或更早版本中,设置参数checkpoint_segments都会很纠结。如果设置为较小的数字,则检查点频繁发生,这会导致性能下降,而如果设置的太大,则WAL文件会占用巨大的磁盘空间,但是其中一些并不是必需的,所以会造成空间浪费。
在9.5版中,WAL文件的管理策略已得到改进,checkpoint_segments参数已经废弃。因此,上述权衡问题也已得到解决。
持续归档是postgresql的一个功能,可在WAL段切换时将WAL段文件复制到归档区域,由归档(后台)进程执行。复制的文件称为归档日志。这项功能通常用于第10章中描述的物理热备份和PITR(时间点恢复)。
归档区域的路径设置为配置参数archive_command。例如,使用以下参数,每当段切换时,WAL段文件都会复制到目录’/home/postgres/archives/’:
archive_command = 'cp %p /home/postgres/archives/%f'
其中,%p被复制WAL段目录的占位符,%f是归档日志文件的占位符。
在切换WAL段文件WAL_7时,WAL_7作为archive log 7复制到归档区域。
参数archive_command可以使用任何Unix命令和工具设置,因此您可以通过scp命令将归档日志传输到其他主机,也可以使用文件备份工具替代普通的复制命令传输至其他主机。
PostgreSQL不会自动清理已经创建的归档日志,因此打开归档时应妥善管理日志。如果你什么都不做,归档日志的数量将持续增加。
pg_archivecleanup是归档日志有效的管理工具之一。