1. 如果没有WAL,pg挂了会怎么样?
1.1 数据库为了快速缓存,实现了共享内存池,若没有实现wal。
1.2 假设第一次插入语句,PG从磁盘中读取数据到内存里。然后向内存里的某一页插入一个元组。目前是个脏页,因为还没有写入到磁盘(也可以是其他持久化介质)。
1.3 然后又来一个插入语句,到这一页。
1.4 如果由于掉电原因,操作系统或者PG挂了,数据就没了。
2. 介绍写wal数据和数据库恢复的过程。
2.1 为了处理上文提到的失败的情况,PG引入了WAL。
2.2 XLOG(历史数据、WALdata),当一个操作如插入删除或提交操作产生,WAL段文件会立刻写到磁盘中。
2.3 xlog 的LSN记录表示一个位置,这条记录写在事务日志里的位置。每个xlog是独一无二的。
2.4 Redo 点,是数据库恢复的点。也就是最后一个检查点。事实上,数据恢复和检查点处理密不可分。
3. wal的概述
(1) 检查点创造者是一个后台进程,周期性执行检查点操作。不管什么时候检查点创造者启动,它就会记录检查点记录到现在的XLog文件。
(2) 假设第一次插入操作,插一条元组到内存页里,写入wal文件。location从LSN_0变为LSN_1。在这个例子中,XLOG是一个头文件和元组实体。
(3) 随着事务提交,PG创建并写了一个Xlog到WALbufer(内存中)然后写到磁盘。LSN变为1
(4) 随着第二次插入操作,PG插入一条新的元组,创建和写入xlog到内存里,并更新表A的LSN变为2。
(5) 当事务提交,PG和步骤3一样。
(6) 假设操作系统失效,所有的内存数据都丢了。
4. pg 恢复的过程
(1) PG读Xlog然后导入到内存里。
(2) PG回放XLOG之前,PG会比较XLOG里的LSN和对应的内存page中的LSN。如果XLOG LSN比页的LSN要大,那么XLOG中的数据会插入到内存页中,并且更新内存页中的LSN。如果LSN比内存页中要小,就读下一个XLOG数据。
(3)PG回放接下来的XLOG。所以redolog 就是xlog。
我们相信写XLOG会有一定开销,但是比起写整个内存页,我们可以得到更好的优化。如系统容错。
5 full-page write
假设表A的数据页被损坏了。因为操作系统问题,后台写进程写了脏页进去。Xlog在当前页就无法恢复了,所以需要full-page write。
full-page write默认是启动的,PostgreSQL在每个检查点之后的每个页面的第一次更改期间将一对标题数据和整个页面写为XLOG记录;就是说对第一次修改做一个备份。
(1) 检查点进程开始一个检查点进程。
(2) 在插入第一条数据,PG操作会写入整个页因为是第一次操作。
(3) 随着事务提交,page会落盘。
(4) 在插入第二条数据后(先日志后数据)。
(5) 然后PG提交。
(6) 为了证明full-page的作用,我们考虑磁盘已经坏死,由于操作系统失误。后台写进程已经写到磁盘。
6 数据库恢复通过备份块
(1) PG读取XLOG的日志到内存里,并且是有数据的。
(2) 当一个Xlog记录是全备份,不管他们的lsn是啥,都会覆盖到内存中。
(3) 由于有操作日志信息,可以恢复B。
7事务日志和WAL段文件
WAL段文件的文件名是如下规则
第一个WAL段文件是000000010000000000000001,如果第一个已经被写满了,那么就会写第二个名为000000010000000000000002。xlog名字都是连续的。0000000100000000000000FF写完后,000000010000000100000000是下一个。 规则大概是这样的。
8 wal段文件的内部
一个WAL文件默认是16MB,且内部的页是8k的。 第一个page有头部数据,叫XLogLongPageHeaderData。其他page头文件叫做XLogPageHeaderData.
typedef struct XLogPageHeaderData
{
uint16 xlp_magic;
uint16 xlp_info;
TimeLineID xlp_tli;
XLogRecPtr xlp_pageaddr; /* XLOG address of this page */
uint32 xlp_rem_len; /* total len of remaining data for record */
} XLogPageHeaderData;
{
uint16 xlp_magic;
uint16 xlp_info;
TimeLineID xlp_tli;
XLogRecPtr xlp_pageaddr; /* XLOG address of this page */
uint32 xlp_rem_len; /* total len of remaining data for record */
} XLogPageHeaderData;
两个结构差不多,就分析这个。
xlp_magic数字用于正确性校验。因为如果被修改了说明数据不对。
xlp_tli表示该page里第一条数据的时间线。
xlp_pageaddr这个page的内存地址。
xlp_rem_len page的空余大小。
9 XLOG 数据记录的内部
typedef struct XLogRecord
{
uint32 xl_tot_len; /* total len of entire record */ 记录长度
TransactionId xl_xid; /* xact id */ 事务id
XLogRecPtr xl_prev; /* ptr to previous record in log */ 指针
uint8 xl_info; /* flag bits, see below */
RmgrId xl_rmid; /* resource manager for this record */资源管理器ID号
/* 2 bytes of padding here, initialize to zero */
pg_crc32c xl_crc; /* CRC for this record */ crc码
}
{
uint32 xl_tot_len; /* total len of entire record */ 记录长度
TransactionId xl_xid; /* xact id */ 事务id
XLogRecPtr xl_prev; /* ptr to previous record in log */ 指针
uint8 xl_info; /* flag bits, see below */
RmgrId xl_rmid; /* resource manager for this record */资源管理器ID号
/* 2 bytes of padding here, initialize to zero */
pg_crc32c xl_crc; /* CRC for this record */ crc码
}
其中 info +rmid 都用于 资源管理。
PG10.0目前有以下操作
Operation | Resource manager |
---|---|
Heap tuple operations | RM_HEAP, RM_HEAP2 |
Index operations | RM_BTREE, RM_HASH, RM_GIN, RM_GIST, RM_SPGIST, RM_BRIN |
Sequence operations | RM_SEQ |
Transaction operations | RM_XACT, RM_MULTIXACT, RM_CLOG, RM_XLOG, RM_COMMIT_TS |
Tablespace operations | RM_SMGR, RM_DBASE, RM_TBLSPC, RM_RELMAP |
replication and hot standby operations | RM_STANDBY, RM_REPLORIGIN, RM_GENERIC_ID, RM_LOGICALMSG_ID |
2. 如果是更新操作,xl_info会设置成XLOG_HEAP_UPDATE。heap_xlog_update()会重放记录。
3. 当事务提交了,xl_rmid 和xl_info 会设置成RM_XACT和XLOG_XACT_COMMIT。恢复时执行xact_redo_commit();
在9.5以后的版本,xl_len已经被移除了。
10. XLOG记录的数据部分(9.4版本和以前)
主要分为两种备份块和非备份块。
备份块,有两个数据结构和一个数据对象。
1. XLogRecord (头部)2. BkpBlock 3.
the entire page apart from its free-space
typedef struct BkpBlock @ include/access/xlog_internal.h { RelFileNode node; /* relation containing block */ ForkNumber fork; /* fork within the relation */ BlockNumber block; /* block number */
// 上面三个属于鉴别哪个表 uint16 hole_offset; /* number of bytes before "hole" */ uint16 hole_length; /* number of bytes in "hole" */ //确定位置和长度 /* ACTUAL BLOCK DATA FOLLOWS AT END OF STRUCT */ } BkpBlock;
非备份块,一个插入操作会生成两个数据结构,一个数据对象。
1. XLogRecord (头部)2.xl_heap_insert标记插入的tuple。(属于哪个表,block位置等)3.数据。
11. XLOG的数据部分(9.5和以后)
9.5以后会划分为头和数据。
头部分会包括0个或多个XLogRecordBlockHeaders和0个或1个 XLogRecordDataHeaderShort(或XLogRecordDataHeaderLong)。当记录有full-page快照(back up block),XLogRecordBlockHeader 包括XLogRecordBlockImageHeader如果是压缩的,还包括XLogRecordBlockCompressHeader。
9.5以后的XLOG 记录
(a)备份块。 1. XLogRecord 头部。 2. XLogRecordBlockHeader等。3. XLogRecordDataHeaderShort 4. 备份块(数据库)5. xl_heap_insert (主要数据)
数据结构主要作用是包含数据,进程号,表ID,偏移量。
非备份块,1. XLogRecord 2. XLogRecordBlockHeader 3. XLogRecordDataHeaderShort 4. an inserted tuple (数据,只有插入的数据没有整个page)5. xl_heap_insert(偏移量)
最后说一下检查点的构造,1.XLogRecord (头部)2.XLogRecordDataHeaderShort 3.CheckPoint (主要数据)
新的数据结构更适合管理。
XLOG记录的写入
INSERT INTO tbl VALUES ('A');
(1) ExtendCLOG()在内存中CLOG中写状态置为"IN_PROGRESS".
(2) heap_insert() 插入一个堆记录到内存池中,创建XLOG记录,并触发XlogInsert。
(3) XLogInsert()写入XLOG LSN_1并且更新pd_lsn从LSN_1.
(4) finish_xact_command(),提交事务,创建XLOG记录,然后XLogInsert() 写入WAL缓存中在LSN_2。
(5)XLogWrite() 写和刷XLOG从内存中刷到文件里。如果标记为"open_sync"或"open_datasync"就异步。如果是fsync就是同步刷。
写入WAL操作可能会被立即触发当发生以下情况,不管事务是否以及提交:
1. 一个正在跑的事务已经被提交或者被取消了。
2. WAL缓存已经被很多许多写过的元组填满。
3. 一个WAL写经常周期性的写。
理所当然地,DML(数据操作语言)操作写入XLOG记录,但非DML操作也是如此。如上所述,提交操作会写入包含已提交事务的id的XLOG记录。(比如说create alter drop truncate comment rename 也是会写xlog的)另一个示例可以是用于写入包含该检查点的一般信息的XLOG记录的检查点动作。此外,SELECT语句在特殊情况下创建XLOG记录,但通常不会创建它们。例如,如果在SELECT语句处理期间删除了不必要的元组并且页面中必要元组的碎片整理由HOT(Heap Only Tuple)发生,则修改页面的XLOG记录将写入WAL缓冲区。(这句话其实我没有很好地理解。)
12. WAL写进程
12. WAL写进程
是一个后台进程,用于定期检查WAL缓冲区并将所有未写入的XLOG记录写入WAL段。此过程的目的是避免突发写入XLOG记录。如果尚未启用此进程,则在一次提交大量数据时,写入XLOG记录可能会遇到瓶颈。该进程是默认工作,且不能被禁止。wal_writer_delay 是间隔,默认是200毫秒。
13 检查点的生成
1. 默认五分钟做一次检查点。可以设置。checkpoint_timeout
2. 在9.4和9.4以前的版本,默认三个段文件做一次检查点。也可以设置。checkpoint_segments
3. 在9.5以后的版本是按照文件大小来算的,默认是1G就是64个文件。
4.在PG开启智能模式或者快速模式下会暂停检查点。
14 检查点处理概述
检查点进程有两个方面:数据库恢复的准备和共享缓冲池上的脏页清除。
(1)检查点过程开始后,REDO点存储在内存中; REDO点是在最新检查点启动时写入XLOG记录的位置,并且是数据库恢复的起点。
(2)该检查点的XLOG记录(即检查点记录)被写入WAL缓冲区。记录的数据部分由结构CheckPoint定义,结构包含几个变量,例如存储在步骤(1)中的REDO点。
(3)所有数据在内存里被刷到底层存储。
(4)所有脏页在共享内存池渐渐地被刷到底层存储。
(5)所有的PG_control文件被更新。这个文件含有基本的包括检查点位置等。
为了从数据库恢复的角度总结上述描述,检查点创建包含REDO点的检查点记录,并将检查点位置和更多内容存储到pg_control文件中。因此,PostgreSQL可以通过从pg_control文件提供的REDO点(从检查点记录获得)重放WAL数据来恢复自身。
15 pg_control file
由于pg_control文件包含检查点的基本信息,因此它对于数据库恢复肯定是必不可少的。如果它被破坏或不可读,则恢复过程无法启动以便无法获得起点。
即使pg_control存了超过40个项目,有三个是必须。
1. state :最新检查点开始时数据库服务器的状态。共有七个状态:“start up”是系统启动的状态; 'shutdown'是系统正常关闭命令正常运行的状态; “in production”是系统运行的状态;等等。
2. Latest checkpoint location : LSN最新检查点记录的位置
3. Prior checkpoint location: 上一个LSN的位置,已经被11版本替代了。11版本只存WAL文件,包括了最新的和上一个检查点。
16 数据恢复
PostgreSQL实现了基于重做日志的恢复功能。如果数据库服务器崩溃,PostgreSQL通过从REDO点顺序重放WAL段文件中的XLOG记录来恢复数据库集群。
(1) PostgreSQL在启动时会读取pg_control文件的所有项目。如果状态项处于'in production',PostgreSQL将进入恢复模式,因为这意味着数据库没有正常停止;如果'close',它将进入正常的启动模式。
(2)PostgreSQL从相应的WAL段文件中读取最新的检查点记录,该记录位于pg_control文件中,并从记录中获取REDO点。如果最新的检查点记录无效,PostgreSQL将读取它之前的记录。如果两个记录都不可读,它会自行放弃恢复。
(3)适当的资源管理器从REDO点按顺序读取和重放XLOG记录,直到它们到达最新WAL段的最后一个点。当重放XLOG记录并且它是备份块时,无论其LSN如何,它都将在相应表的页面上被覆盖。否则,仅当此记录的LSN大于相应页面的pd_lsn时,才会重放(非备份块)XLOG记录。
LSN的比较:
1. PG插入一个元组到表A,写一条XLOG在LSN_1的位置。
2. 后台写进程把表A的页刷到存储。此时pd_lsn是1。
3. PG插入一个元组到表A,并且写一个XLOG记录到LSN_2.(先写XLOG文件,数据还没有刷进去)此时立刻挂掉,然后启动!
(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时,才应重放非备份块记录。
17 WAL段文件管理
PostgreSQL将XLOG记录写入存储在pg_xlog子目录(版本10或更高版本,pg_wal子目录)中的一个WAL段文件中,如果旧文件满了,则切换为新文件。 WAL文件的数量将根据几个配置参数。
WAL 段文件切换(啥时候换下一个文件)
1. WAL文件写满了。
2. pg_switch_xlog函数。
3. archive模式开启,并且有时间限制时,时间触发。
WAL段文件管理(9.5及以后)
每当检查点启动时,PostgreSQL都会估计并准备下一个检查点周期所需的WAL段文件数。这种估计是关于先前检查点周期中消耗的文件数量。然后他会自动删除WAL。
当wal快没的时候就会创建下一个wal文件用于写。当xlog达到max_wal_size,会自动做checkpoint。
WAL段文件管理(9.4及以前)是通过一套公式。
18 持续归档和归档日志
连续存档是一种功能,可在WAL段切换时将WAL段文件复制到存档区域,并由存档(后台)进程执行。复制的文件称为存档日志。此功能通常用于热物理备份和PITR。
拷贝的目录主要是/home/postgres/archives
参数archive_command可以设置任何Unix命令和工具,因此您可以通过设置scp命令或任何文件备份工具而不是普通的复制命令将存档日志传输到其他主机。