预写日志——WAL

预写日志——WAL
事务日志是数据库的重要组成部分,因为所有的数据库管理系统都要求即使发生系统故障也不丢失任何数据。它是数据库系统中所有更改和操作的历史日志,以确保没有数据因故障而丢失,例如电源故障或其他导致服务器崩溃的服务器故障。由于日志包含有关已执行的每个事务的足够信息,因此数据库服务器应该能够通过重播事务日志中的更改和操作来恢复数据库集群,以防服务器崩溃。

在计算机科学领域,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数据的内部布局
WAL数据的写入
WAL 编写器进程
检查点处理
数据库恢复处理
管理 WAL 段文件
连续归档
9.1。概述
让我们看一下WAL机制的概述。为了澄清 WAL 一直在处理的问题,第一小节显示了如果 PostgreSQL 不实现 WAL 发生崩溃时会发生什么。第二小节介绍了一些关键概念,并展示了本章主要主题的概述,WAL 数据的写入和数据库恢复处理。最后小节完成了 WAL 的概述,增加了一个关键概念。

在本节中,为了简化描述,使用了仅包含一页的表 TABLE_A。

9.1.1。没有 WAL 的插入操作
如第 8 章所述,为了提供对关系页面的有效访问,每个 DBMS 都实现了共享缓冲池。

假设我们在没有实现 WAL 特性的 PostgreSQL 上的 TABLE_A 中插入一些数据元组;这种情况如图 9.1 所示。
(1) 发出第一个 INSERT 语句,PostgreSQL 将 TABLE_A 的页面从数据库集群加载到内存共享缓冲池中,并在页面中插入一个元组。该页面不会立即写入数据库集群。(如第 8 章所述,修改后的页面通常称为脏页。)
(2) 发出第二个 INSERT 语句,PostgreSQL 将一个新的元组插入到缓冲池上的页面中。此页面尚未写入存储。
(3) 如果操作系统或PostgreSQL服务器由于电源故障等任何原因出现故障,所有插入的数据都将丢失。
因此,没有 WAL 的数据库容易受到系统故障的影响。

历史信息

在引入 WAL 之前(7.0 或更早版本),PostgreSQL 通过在内存中更改页面时发出同步系统调用来对磁盘进行同步写入,以确保持久性。因此,INSERT 和 UPDATE 等修改命令的性能非常差。

9.1.2. 插入操作和数据库恢复
为了在不影响性能的情况下处理上述系统故障,PostgreSQL 支持 WAL。本小节介绍了一些关键词和关键概念,然后介绍了 WAL 数据的写入和数据库的恢复。

PostgreSQL 将所有修改作为历史数据写入持久存储中,为失败做准备。在 PostgreSQL 中,历史数据称为XLOG 记录或WAL 数据。

XLOG 记录通过插入、删除或提交等更改操作 写入内存中的WAL 缓冲区。当事务提交/中止时,它们会立即写入存储上的WAL 段文件。(准确地说,XLOG记录的写入也可能发生在其他情况下。详细内容将在9.5节中描述。​​) XLOG记录的LSN(Log Sequence Number)表示其记录在事务日志中写入的位置。记录的 LSN 用作 XLOG 记录的唯一 ID。

顺便说一句,当我们考虑数据库系统如何恢复时,可能有一个问题;PostgreSQL 从什么时候开始恢复?答案是重做点;也就是最近一次checkpoint启动时写入XLOG记录的位置(PostgreSQL中的checkpoint在第9.7节中有介绍)。事实上,数据库恢复处理与检查点处理密切相关,这两个处理是密不可分的。

WAL 和 checkpoint 过程在 7.1 版本中同时实现。

由于主要关键字和概念的介绍刚刚结束,接下来将介绍使用 WAL 进行元组插入。请参见图 9.2 和以下说明。(另请参阅此幻灯片。)

在这里插入图片描述

符号

' TABLE_A's LSN ' 显示了 TABLE_A 的页眉中 pd_lsn 的值。' page's LSN ' 也是同样的方式。

(1) checkpointer,一个后台进程,周期性地执行checkpointing。每当检查点启动时,它都会将称为检查点记录的 XLOG 记录写入当前 WAL 段。该记录包含最新的REDO 点的位置。
(2) 发出第一条 INSERT 语句,PostgreSQL 将 TABLE_A 的页面加载到共享缓冲池中,在页面中插入一个元组,创建该语句的 XLOG 记录并将其写入 WAL 缓冲区的LSN_1位置,并更新 TABLE_A 的 LSN从LSN_0到LSN_1。
在这个例子中,这个 XLOG 记录是一对 header-data 和整个元组。
(3) 当这个事务提交时,PostgreSQL 创建这个提交动作的 XLOG 记录并将其写入 WAL 缓冲区,然后,将 WAL 缓冲区上的所有 XLOG 记录写入并刷新到 WAL 段文件,从LSN_1。
(4) 发出第二个 INSERT 语句,PostgreSQL 将一个新的元组插入到页面中,创建该元组的 XLOG 记录并将其写入 WAL 缓冲区的LSN_2,并将 TABLE_A 的 LSN 从LSN_1更新为LSN_2。
(5) 当该语句的事务提交时,PostgreSQL 的操作方式与步骤(3) 相同。
(6) 想象一下操作系统故障应该在什么时候发生。即使共享缓冲池上的所有数据都丢失了,页面的所有修改都已作为历史数据写入 WAL 段文件。
以下说明显示了如何将我们的数据库集群恢复到崩溃前的状态。不需要做任何特别的事情,因为 PostgreSQL 会通过重新启动自动进入恢复模式。参见图 9.3(和这张幻灯片)。PostgreSQL 将从 REDO 点按顺序读取和重放相应 WAL 段文件中的 XLOG 记录。

在这里插入图片描述

(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_1。
(3) PostgreSQL 以同样的方式重放剩余的 XLOG 记录。
PostgreSQL 可以通过按时间顺序重放 WAL 段文件中写入的 XLOG 记录来恢复自身。因此,PostgreSQL 的 XLOG 记录显然是REDO log。

PostgreSQL 不支持 UNDO 日志。

虽然写 XLOG 记录肯定要花一些钱,但和写整个修改过的页面相比,这算不了什么。我们确信我们可以获得比我们支付的金额更大的好处,即系统故障容忍度。

9.1.3. 整页写入
假设存储中 TABLE_A 的页面数据已损坏,因为在后台写入器进程一直在写入脏页面时操作系统出现故障。由于无法在损坏的页面上重放 XLOG 记录,我们需要一个附加功能。

PostgreSQL 支持一种称为整页写入的功能来处理此类故障。如果启用,PostgreSQL在每个检查点后的每个页面的第一次更改期间将一对 header-data 和整个页面作为 XLOG 记录写入;默认启用。在 PostgreSQL 中,这样一个包含整个页面的 XLOG 记录被称为备份块(或整页图像)。

让我们再次描述元组的插入,但启用了整页写入。请参见图 9.4 和以下说明。

在这里插入图片描述

(1) checkpointer 启动一个检查点进程。
(2) 在插入第一条 INSERT 语句时,虽然 PostgreSQL 的操作方式与上一小节几乎相同,但这条 XLOG 记录是该页面的备份块(即它包含整个页面),因为这是在最新的检查点之后第一次写这个页面。
(3) 当这个事务提交时,PostgreSQL 以与上一小节相同的方式运行。
(4) 在第二个 INSERT 语句的插入中,PostgreSQL 的操作方式与上一小节相同,因为该 XLOG 记录不是备份块。
(5) 当该语句的事务提交时,PostgreSQL 的操作方式与上一小节相同。
(6) 为了证明整页写入的有效性,这里我们考虑在后台写入器将其写入硬盘时,由于操作系统故障而导致存储上的 TABLE_A 页面损坏的情况。
重新启动 PostgreSQL 服务器以修复损坏的集群。请参见图 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 也可以恢复数据库。

WAL、备份和复制

如上所述,WAL 可以防止由于进程或操作系统宕机而导致的数据丢失。但是,如果发生文件系统或媒体故障,数据将会丢失。为了应对此类故障,PostgreSQL 提供了 在线备份 和 复制功能。

如果定期进行在线备份,即使发生介质故障,也可以从最近的备份中恢复数据库。但是,请注意,在进行最后一次备份后所做的更改无法恢复。

同步复制功能可以将所有更改实时存储到另一个存储或主机。

有关详细信息,请分别参见第10章和第11章。

9.2. 事务日志和 WAL 段文件
从逻辑上讲,PostgreSQL 将 XLOG 记录写入事务日志,这是一个 8 字节长(16 ExaByte)的虚拟文件。

由于事务日志的容量实际上是无限的,因此可以说 8 字节的地址空间足够大,我们不可能处理 8 字节长度的文件。因此,PostgreSQL 中的事务日志默认分为 16 兆字节的文件,每个文件称为

WAL 段文件大小

在版本 11 或更高版本中,通过 initdb 命令创建 PostgreSQL 集群时, 可以使用--wal-segsize选项配置 WAL 段文件的大小。

在这里插入图片描述

WAL段文件名是16进制的24位数字,命名规则如下:

在这里插入图片描述

时间线 ID

PostgreSQL 的 WAL 包含时间线 ID (4 字节无符号整数)的概念,用于第 10 章中描述的时间点恢复(PITR)。但是本章中timelineId固定为0x00000001,因为后面的描述中不需要这个概念。

第一个 WAL 段文件是 00000001 0000000 0000000 01。如果第一个已被写入 XLOG 记录填满,则将提供第二个 00000001 00000000 000000 02。后继文件按升序依次使用,00000001 00000000 000000 FF填满后,将提供下一个 00000001 00000001 000000 00 。这样,每当最后 2 位结转时,中间 8 位数字就会增加 1。

同样,在 00000001 00000001 000000 FF被填满后,将提供 00000001 00000002 000000 00,以此类推。

pg_xlogfile_name / pg_walfile_name

使用内置函数pg_xlogfile_name(9.6 或更早版本)或pg_walfile_name(10 或更高版本),我们可以找到包含指定 LSN 的 WAL 段文件名。一个例子如下所示:
testdb =# SELECT pg_xlogfile_name ( '1/00002D3E' ); # 在版本 10 或更高版本中,“SELECT pg_walfile_name('1/00002D3E');”  
     pg_xlogfile_name     
-------------------------- 000000010000000100000000 (1行)

9.3. WAL 段的内部布局
默认情况下,WAL 段是一个 16 MB 的文件,它在内部分为 8192 字节 (8 KB) 的页面。第一页具有由结构XLogLongPageHeaderData定义的标题数据,而所有其他页面的标题具有由结构XLogPageHeaderData定义的页面信息。在页眉之后,XLOG 记录按降序从头开始写入每一页。参见图 9.7。

在这里插入图片描述

XLogLongPageHeaderData 结构和 XLogPageHeaderData 结构在src/include/access/xlog_internal.h中定义。由于在以下描述中不需要这两种结构,因此省略了对这两种结构的解释。

9.4。XLOG 记录的内部布局
XLOG 记录包括通用标题部分和每个相关的数据部分。第一个小节描述了标题结构;其余两个小节分别解释了 9.4 或更早版本和 9.5 版本中数据部分的结构。(数据格式在 9.5 版中已更改。)

9.4.1。XLOG 记录的标题部分
所有 XLOG 记录都有一个由结构 XLogRecord 定义的通用标题部分。此处,9.4 及更早版本的结构如下所示,但在 9.5 版本中有所更改。

typedef struct XLogRecord { 
   uint32 xl_tot_len ; /* 整个记录的总长度 */ TransactionId    xl_xid ; /* xact id */ 
   uint32 xl_len ; /* rmgr 数据的总长度 */ 
   uint8 xl_info ; /* 标志位,见下文 */ RmgrId           xl_rmid ; /* 这条记录的资源管理器 */ /* 这里有 2 个字节的填充,初始化为零 */ XLogRecPtr       xl_prev ; /* 指向日志中的前一条记录 */ 
   pg_crc32 xl_crc ; /* 这条记录的 CRC */ }  

XLog记录;
除了两个变量之外,大部分变量都非常明显,无需描述。

xl_rmid和xl_info都是与资源管理器相关的变量,它们是与 WAL 特性相关的操作的集合,例如XLOG记录的写入和重放。资源管理器的数量随着每个 PostgreSQL 版本的增加而增加,版本 10 包含以下它们:

以下是资源管理器在以下方面如何工作的一些代表性示例:

如果发出 INSERT 语句,则其 XLOG 记录的头变量xl_rmid和xl_info分别设置为“RM_HEAP”和

  • “XLOG_HEAP_INSERT”。恢复数据库集群时,根据xl_info选择的RM_HEAP的函数heap_xlog_insert()会重放这条XLOG记录。
  • 虽然 UPDATE 语句类似,但 XLOG 记录的头变量xl_info设置为’XLOG_HEAP_UPDATE’,并且 RM_HEAP 的函数heap_xlog_update()在数据库恢复时重放其记录。
  • 当事务提交时,其 XLOG 记录的头变量xl_rmid和xl_info分别设置为“RM_XACT”和“XLOG_XACT_COMMIT”。恢复数据库集群时,函数xact_redo_commit() 会重放这条记录。
    在 9.5 或更高版本中,已删除了一个变量 (xl_len) 结构 XLogRecord以改进 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中定义。

9.4.2. XLOG 记录的数据部分(9.4 或更早版本)
XLOG 记录的数据部分分为备份块(整个页面)或非备份块(操作不同的数据)。

在这里插入图片描述

下面使用一些具体示例描述 XLOG 记录的内部布局。

9.4.2.1。备份块
备份块如图 9.8(a) 所示。它由两个数据结构和一个数据对象组成,如下图所示:

结构 XLogRecord(标题部分)
结构BkpBlock
整个页面,除了其可用空间
BkpBlock包含用于在数据库集群中标识该页面的变量(即包含该页面的关系的relfilenode和fork 编号,以及该页面的块编号),以及该页面的空闲空间的开始位置和长度。

9.4.2.2。非备份块
在非备份块中,数据部分的布局因每个操作而异。在此,以 INSERT 语句的 XLOG 记录为例进行说明。参见图 9.8(b)。在这种情况下,INSERT 语句的 XLOG 记录由两个数据结构和一个数据对象组成,如下所示:

结构 XLogRecord(标题部分)
结构xl_heap_insert
插入的元组——准确地说,从元组中删除了几个字节
结构xl_heap_insert包含用于标识数据库集群中插入的元组的变量(即包含该元组的表的relfilenode和该元组的tid),以及该元组的可见性标志。

结构 xl_heap_header 的源代码注释中描述了从插入的元组中删除几个字节的原因:

我们不会在 WAL 中存储插入或更新元组的整个固定部分(HeapTupleHeaderData);我们可以通过重构 WAL 记录中其他地方可用的字段来节省一些字节,或者可能只是简单地不需要重构。

这里再举一个例子。参见图 9.8©。Checkpoint 记录的 XLOG 记录相当简单;它由两个数据结构组成,如下所示:

结构 XLogRecord(标题部分)
包含其检查点信息的检查点结构(请参阅第 9.7 节中的更多详细信息)

xl_heap_header 结构在src/include/access/htup.h中定义,而 CheckPoint 结构在src/include/catalog/pg_control.h中定义。

9.4.3。XLOG 记录的数据部分(9.5 或更高版本)
在 9.4 或更早的版本中,没有通用的 XLOG 记录格式,因此每个资源管理器都必须定义自己的格式。在这种情况下,维护源代码和实现与 WAL 相关的新功能变得越来越困难。为了解决这个问题,9.5版本引入了一种不依赖资源管理器的通用结构化格式。

XLOG 记录的数据部分可以分为头和数据两部分。请参见图 9.9。
在这里插入图片描述

Header 部分包含零个或多个 XLogRecordBlockHeaders和零个或一个 XLogRecordDataHeaderShort(或 XLogRecordDataHeaderLong);它必须至少包含其中之一。当其记录存储整页图像(即备份块)时, XLogRecordBlockHeader 包含XLogRecordBlockImageHeader ,如果其块被压缩 ,则还包含XLogRecordBlockCompressHeader 。

数据部分由零个或多个块数据和零个或一个主数据组成,分别对应XLogRecordBlockHeader(s)和XLogRecordDataHeader。

WAL 压缩
在 9.5 或更高版本中,可以通过设置参数 wal_compression = enable 使用 LZ 压缩方法压缩 XLOG 记录中的整页图像。在这种情况下,将添加结构 XLogRecordBlockCompressHeader。

此功能有两个优点和一个缺点。优点是减少写入记录的 I/O 成本和抑制 WAL 段文件的消耗。缺点是要消耗大量的 CPU 资源进行压缩。

在这里插入图片描述

下面显示了一些具体示例,如上一小节所示。

9.4.3.1。备份块
INSERT 语句创建的备份块如图 9.10(a) 所示。它由四个数据结构和一个数据对象组成,如下图所示:

结构 XLogRecord(标题部分)
结构 XLogRecordBlockHeader 包括一个 LogRecordBlockImageHeader
结构 XLogRecordDataHeaderShort
一个备份块(块数据)
结构 xl_heap_insert(主要数据)
XLogRecordBlockHeader 包含用于标识数据库集群中的块的变量(relfilenode、fork number和block number);XLogRecordImageHeader 包含此块的长度和偏移量。(这两个头结构一起可以存储直到版本 9.4 使用的 BkpBlock 的相同数据。)

XLogRecordDataHeaderShort 存储的是记录的主要数据xl_heap_insert结构的长度。(看以下。)

包含整页图像的 XLOG 记录的主要数据除了在某些特殊情况下(例如在逻辑解码和推测插入中)外不使用。重放这条记录时忽略它,这是冗余数据。将来可能会有所改进。

此外,备份块记录的主要数据依赖于创建它们的语句。例如, UPDATE 语句附加xl_heap_lock或xl_heap_updated。

9.4.3.2。非备份块
接下来,将描述由 INSERT 语句创建的非备份块记录(另请参见图 9.10(b))。它由四个数据结构和一个数据对象组成,如下图所示:

结构 XLogRecord(标题部分)
结构 XLogRecordBlockHeader
结构 XLogRecordDataHeaderShort
一个插入的元组(确切地说,是一个 xl_heap_header 结构和一个插入的数据整体)
结构xl_heap_insert(主要数据)
XLogRecordBlockHeader 包含三个值(relfilenode、fork number和block number)来指定插入元组的块,以及插入元组的数据部分的长度。XLogRecordDataHeaderShort 包含新的xl_heap_insert结构的长度,它是这条记录的主要数据。

新的xl_heap_insert仅包含两个值:块内此元组的偏移量和可见性标志;它变得非常简单,因为 XLogRecordBlockHeader 存储了旧数据中包含的大部分数据。

作为最后一个例子,检查点记录如图 9.10© 所示。它由三个数据结构组成,如下图所示:

结构 XLogRecord(标题部分)
包含主要数据长度的结构 XLogRecordDataHeaderShort
结构 CheckPoint(主要数据)

结构xl_heap_header定义在src/include/access/htup.h中,CheckPoint 结构定义在src/include/catalog/pg_control.h中。

虽然新格式对我们来说有点复杂,但它为资源管理器的解析器设计得很好,而且许多类型的 XLOG 记录的大小通常比以前的要小。主要结构的尺寸如图所示。9.8 和 9.10,因此您可以计算这些记录的大小并相互比较。(新检查点的大小比前一个大,但包含更多变量。)

9.5。XLOG 记录的写入
完成了热身练习,现在我们准备了解 XLOG 记录的编写。因此,我将在本节中尽可能准确地解释它。

首先,发出以下语句来探索 PostgreSQL 内部:

testdb =# INSERT INTO tbl VALUES ( ‘A’ );
通过发出上述语句,调用内部函数exec_simple_query();exec_simple_query()的伪代码如下所示:

exec_simple_query () @postgres 。C 

(1 )扩展CLOG ()@clog 。c                   /* 写入本次交易的状态  
                                           *“IN_PROGRESS”到 CLOG。
                                           */ ( 2 ) heap_insert () @heapam 。c                 /* 插入一个元组,创建一个 XLOG 记录,

                                           * 并调用函数 XLogInsert。
                                           */ ( 3 ) XLogInsert () @xlog 。c ( 9.5或更高版本, xloginsert . c ) /* 写入插入元组的 XLOG 记录
     
                                          
                                           * 到 WAL 缓冲区,并更新页面的 pd_lsn。
                                           */ (4 )finish_xact_command ()@postgres 。c      /* 调用提交动作。*/ XLogInsert () @xlog . c   ( 9.5或更高版本, xloginsert . c ) /* 写入此提交操作的 XLOG 记录
    
        
                                          
                                           * 到 WAL 缓冲区。
                                           */ ( 5 ) XLogWrite () @xlog 。c                  /* 写入并刷新所有 XLOG 记录
    
                                           * WAL 缓冲区到 WAL 段。
                                           */ ( 6 ) TransactionIdCommitTree () @transam 。c   /* 改变这个事务的状态
  
                                           * 从 CLOG 上的“IN_PROGRESS”到“COMMITTED”。
                                           */

在下面的段落中,将解释每一行伪代码,以了解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’,则相应的系统调用 - fsync()、带有 F_FULLFSYNC 选项的 fcntl() 或fdatasync()- 将被执行。在任何情况下,确保所有 XLOG 记录都写入存储。
(6) 函数TransactionIdCommitTree()在 CLOG上将此事务的状态从“IN_PROGRESS”更改为“COMMITTED”。
在这里插入图片描述

在上面的例子中,commit action 导致了 XLOG 记录写入 WAL 段,但是当以下任何一种情况发生时,可能会导致这种写入:

一个正在运行的事务已提交或已中止。
WAL 缓冲区已被许多已写入的元组填满。(WAL 缓冲区大小设置为参数wal_buffers。)
WAL 写入器进程定期写入。(见下一节。)
如果发生上述情况之一,则 WAL 缓冲区上的所有 WAL 记录都将写入 WAL 段文件,无论它们的事务是否已提交。

DML(数据操作语言)操作写入 XLOG 记录是理所当然的,但非 DML 操作也是如此。如上所述,提交操作会写入包含已提交事务 ID 的 XLOG 记录。另一个示例可能是一个检查点操作,用于写入包含该检查点的一般信息的 XLOG 记录。此外,SELECT 语句在特殊情况下会创建 XLOG 记录,尽管它通常不会创建它们。例如,如果在 SELECT 语句处理过程中,HOT(Heap Only Tuple) 发生了对页中不必要元组的删除和必要元组的碎片整理,则将修改页的 XLOG 记录写入 WAL 缓冲区。

9.6。WAL 编写器进程
WAL writer 是一个后台进程,它定期检查 WAL 缓冲区并将所有未写入的 XLOG 记录写入 WAL 段。此过程的目的是避免 XLOG 记录的突发写入。如果没有开启这个过程,那么在一次提交大量数据时,XLOG 记录的写入可能会遇到瓶颈。

WAL 编写器默认工作,无法禁用。检查间隔设置为配置参数wal_writer_delay,默认值为 200 毫秒。

9.7。PostgreSQL 中的检查点处理
在 PostgreSQL 中,检查点(后台)进程执行检查点;当发生以下情况之一时,其过程开始:

checkpoint_timeout从上一个检查点 设置的间隔时间已经过去(默认间隔为 300 秒(5 分钟))。
在 9.4 或更早的版本中,为checkpoint_segments 设置的 WAL 段文件的数量自上一个检查点以来已被消耗(默认数量为 3)。
在 9.5 或更高版本中,pg_xlog(在 10 或更高版本中为 pg_wal)中的 WAL 段文件的总大小已超过参数max_wal_size的值(默认值为 1GB(64 个文件))。
PostgreSQL 服务器以智能或快速模式停止。
当超级用户手动发出 CHECKPOINT 命令时,它的过程也会执行此操作。

在 9.1 或更早版本中,如中所述在第 8.6 节中,后台写入进程同时进行了检查和脏页写入。

在以下小节中,将描述检查点的概要和保存当前检查点元数据的 pg_control 文件。

9.7.1。检查点处理概要
检查点过程有两个方面:数据库恢复的准备,以及共享缓冲池上脏页的清理。在本小节中,将重点介绍其内部处理。请参见图 9.13 和以下说明。

在这里插入图片描述

(1) 一个检查点进程开始后,REDO点存储在内存中;REDO点是最近一次checkpoint启动时写入XLOG记录的位置,是数据库恢复的起点。
(2)将该检查点的一条XLOG记录(即检查点记录)写入WAL缓冲区。记录的数据部分由结构CheckPoint定义,该结构包含几个变量,例如与步骤 (1) 一起存储的 REDO 点。
另外,写检查点记录的位置,字面意思就是检查点。
(3) 共享内存中的所有数据(例如clog的内容等)都被刷新到存储中。
(4) 共享缓冲池上的所有脏页都被逐渐写入并刷新到存储中。
(5) pg_control 文件被更新。该文件包含基本信息,例如检查点记录写入的位置(也称为检查点位置)。这个文件的细节稍后再说。
从数据库恢复的角度总结上面的描述,检查点创建包含重做点的检查点记录,并将检查点位置等存储到pg_control 文件中。因此,PostgreSQL 可以通过重放 pg_control 文件提供的 REDO 点(从检查点记录中获得)中的 WAL 数据来恢复自身。

9.7.2。pg_control 文件
由于pg_control 文件包含检查点的基本信息,因此它对于数据库恢复当然是必不可少的。如果它被损坏或无法读取,则恢复过程无法启动,从而无法获得起点。

尽管pg_control 文件存储了 40 多个项目,但下一节中需要的三个项目如下所示:

状态– 最近一次检查点开始时数据库服务器的状态。共有七种状态:“启动”是系统正在启动的状态;'shutdown’是系统被shutdown命令正常关闭的状态;“生产中”是系统正在运行的状态;等等。
最新检查点位置– 最新检查点记录的 LSN 位置。
先前检查点位置- 先前检查点记录的 LSN 位置。请注意,它在版本 11 中已弃用;细节描述在以下。
pg_control 文件存储在基本目录下的全局子目录中;可以使用pg_controldata实用程序显示其内容。

postgres > pg_controldata   / usr / local / pgsql / data
pg_control 版本号:937目录版本号:201405111数据库系统标识符:6035535450242021944数据库集群状态:生产中            
               
           
               
pg_control最后修改时间:Mon Jul 25 15:16:38 2022最新检查点位置:0 / C000F48先前
检查点位置:0 / C000E70                 
                       

...剪断...

删除 PostgreSQL 11 中的先前检查点
PostgreSQL 11 或更高版本将只存储包含最新检查点或更新的 WAL 段;包含先前检查点的旧段文件将不被存储,以减少磁盘空间,用于在 pg_xlog(pg_wal) 子目录下保存 WAL 段文件。详细看这个线程 。

9.8。PostgreSQL 中的数据库恢复
PostgreSQL 实现了基于重做日志的恢复特性。如果数据库服务器崩溃,PostgreSQL 通过从 REDO 点顺序重放 WAL 段文件中的 XLOG 记录来恢复数据库集群。

到本节为止,我们已经多次讨论过数据库恢复,所以我将描述关于恢复的两件事,但还没有解释。

首先是 PostgreSQL 如何开始恢复过程。当 PostgreSQL 启动时,它首先读取 pg_control 文件。以下是从那时起的恢复处理的细节。请参见图 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 的页面已写入存储。

使用立即模式关闭,然后启动。

在这里插入图片描述

(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。

9.9。WAL 段文件管理
PostgreSQL 将 XLOG 记录写入存储在 pg_xlog 子目录(版本 10 或更高版本,pg_wal 子目录)中的 WAL 段文件之一,如果旧文件已满,则切换到新文件。WAL 文件的数量将根据几个配置参数以及服务器活动而有所不同。此外,他们的管理策略在 9.5 版本中得到了改进。

在以下小节中,将描述 WAL 段文件的切换和管理。

9.9.1。WAL 段开关
WAL 段切换发生在以下情况之一发生时:

WAL 段已被填满。
功能pg_switch_xlog已发布。
archive_mode已启用,并且已超过设置为archive_timeout的时间。
切换的文件通常会被回收(重命名和重用)以供将来使用,但如果没有必要,它可能会在以后被删除。

9.9.2。9.5 或更高版本中的 WAL 段管理
每当检查点开始时,PostgreSQL 都会估计并准备下一个检查点周期所需的 WAL 段文件的数量。此类估计是针对先前检查点周期中消耗的文件数进行的。它们是从包含前一个 REDO 点的段开始计算的,其值介于min_wal_size(默认情况下,80 MB,即 5 个文件)和max_wal_size(1 GB,即 64 个文件)之间。如果检查点启动,必要的文件将被保留或回收,而不必要的文件将被删除。

一个具体的例子如图 9.17 所示。假设 checkpoint 开始前有六个文件,WAL_3包含之前的 REDO 点(在 10 或更早版本;在 11 或更高版本,REDO 点),PostgreSQL 估计需要五个文件。在这种情况下,WAL_1将被重命名为WAL_7以便回收,而WAL_2将被删除。

可以删除比包含先前 REDO 点的文件更旧的文件,因为从第 9.8 节中描述的恢复机制中可以清楚地看出,它们永远不会被使用。

在这里插入图片描述

如果由于 WAL 活动激增而需要更多文件,则将在 WAL 文件的总大小小于max_wal_size时创建新文件。例如,在图 9.18 中,如果WAL_7已被填满,则新创建 WAL_8 。

在这里插入图片描述

WAL 文件的数量根据服务器活动自适应地变化。如果 WAL 数据写入量不断增加,WAL 段文件的估计数量以及 WAL 文件的总大小也会逐渐增加。在相反的情况下(即 WAL 数据写入量减少),这些减少。

如果 WAL 文件的总大小超过max_wal_size,将启动一个检查点。图 9.19 说明了这种情况。通过检查点,将创建一个新的重做点,最后一个重做点将是前一个;然后将回收不必要的旧文件。这样,PostgreSQL 将始终只保存数据库恢复所需的 WAL 段文件。

在这里插入图片描述

配置参数wal_keep_segments和复制槽特性也会影响 WAL 段文件的数量。

9.9.3。9.4 或更早版本中的 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文件的数量已被消耗时,就会发生检查点过程。因此保证两个或多个 REDO 点总是包含在 WAL 文件中,因为文件的数量总是大于
2
×
checkpoint_segments
. 如果它是通过超时发生的,也是如此。因此,PostgreSQL 将始终保存恢复所需的足够多的 WAL 段文件(有时超过必要)。

在 9.4 或更早的版本中,参数checkpoint_segments令人头疼。如果设置得小,checkpoint会频繁出现,导致性能下降,而如果设置得大,WAL文件总是需要巨大的磁盘空间,其中一些并不总是必要的。

在 9.5 版本中,WAL 文件的管理策略得到了改进,checkpoint_segments已经过时。因此,上述权衡问题也得到了解决。

9.10。连续归档和归档日志
Continuous Archiving是在 WAL 段切换时将 WAL 段文件复制到归档区的功能,由归档器(后台)进程执行。复制的文件称为存档日志。此功能通常用于第 10 章中描述的热物理备份和 PITR(Point-in-Time Recovery) 。

归档区域的路径设置为配置参数archive_command。例如,使用以下参数,WAL 段文件在每次每个段切换时 复制到目录’/home/postgres/archives/’ :

archive_command = ‘cp %p /home/postgres/archives/%f’
其中,占位符%p是复制的 WAL 段,%f是归档日志。

在这里插入图片描述

当切换 WAL 段文件WAL_7时,该文件将作为存档日志 7复制到存档区域。
参数archive_command可以设置任何Unix 命令和工具,因此您可以通过设置scp 命令或任何文件备份工具而不是普通的copy 命令将归档日志传输到其他主机。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值