Postgresql之WAL log机制
WAL机制简介WAL即 Write-Ahead Logging,是一种实现事务日志的标准方法。WAL 的中心思想是先写日志,再写数据,数据文件的修改必须发生在这些修改已经记录在日志文件中之后。采用WAL日志的数据库系统在事务提交时,WAL机制可以从两个方面来提高性能:
总体来说,使用了WAL机制之后,磁盘写操作只有传统的回滚日志的一半左右,大大提高了数据库磁盘I/O操作的效率,从而提高了数据库的性能。 采用了WAL机制,就不需要在每次事务提交的时候都把数据页冲刷到磁盘,如果出现数据库崩溃, 我们可以用日志来恢复数据库,任何尚未附加到数据页的记录都将先从日志记录中重做(这叫向前滚动恢复,也叫做 REDO)。对于PostgreSQL来说,未采用WAL机制之前,如果数据库崩溃,可能存在数据页不完整的风险,而WAL 在日志里保存整个数据页的内容,完美地解决了这个问题。 WAL机制实现实现WAL机制,需要保证脏页在刷新到磁盘前,该数据页相对应的日志记录已经刷新到磁盘中。为了实现WAL机制,当PostgreSQL进行事务提交(脏数据页需要刷新到磁盘)时,需要进行如下操作:
LSN标记为了标记每个数据页最后修改它的日志记录号,在每个数据页的PageHeaderData结构中引入了一个LSN标记,如下: typedef struct PageHeaderData
{
PageXLogRecPtr pd_lsn; //指向最后修改页面的日志记录
uint16 pd_checksum;
uint16 pd_flags;
LocationIndex pd_lower;
LocationIndex pd_upper;
LocationIndex pd_special;
uint16 pd_pagesize_version;
TransactionId pd_prune_xid;
ItemIdData pd_linp[1];
} PageHeaderData;
其中PageXLogRecPtr结构是一个无符号的64位整数,它的含义如下:
PageXLogRecPtr结构和每个日志记录一一对应,同时LSN是全局统一管理,顺序增加的。 当缓冲区管理器(Bufmgr)写出脏数据页时,必须确保小于页面PageHeaderData中pd_lsn指向的Xlog日志已经刷写到磁盘上了。这里的LSN检查只用于共享缓冲区,用于临时表的local缓冲区不需要,因此临时表是没有WAL日志的,不受WAL机制的保护。 WAL共享缓存区为了进一步的减少xlog日志文件的I/O操作,PostgreSQL中引入了WAL共享缓存区,对产生的xlog日志进行缓存,合并I/O操作。 WAL共享缓存区的大小可以通过设置postgresql.conf文件参数wal_buffers来设置,官方解释如下:
可以看出,当多个client同时进行事务提交时,如果这个缓存区比较大,相应地会更大程度地合并I/O,提高性能,但是如果过大,同时也存在断电后,这部分数据丢失的风险。所以,这里比较推荐使用默认值,即shared_buffers的1/32。 为了标示当前WAL共享缓存区的状态,引入了XLogCtlData结构如下: struct XLogCtlData
{
XLogwrtRqst LogwrtRqst;/* 表示当前请求写入系统缓冲区或同步写入磁盘的日志位置*/
XLogRecPtr asyncXactLSN; /*最近需要异步提交的日志位置*/
XLogwrtResult LogwrtResult; /*当前已经写入系统缓冲区或者同步写入磁盘的日志位置*/
XLogRecPtr *xlblocks; /* LSN数组*/
int XLogCacheBlck;/* WAL缓存区的大小,单位为页 */
char *pages; /* 指向WAL缓存Buffer的首地址 */
......
}
其中,LogwrtRqst表示当前请求写入系统缓冲区或同步写入磁盘的日志位置,由info_lck轻量锁保护,结构如下: struct XLogwrtRqst { XLogRecPtr Write; XLogRecPtr Flush; } XLogwrtRqst; 这里需要注意的是,因为很多操作系统会维护一个操作系统缓存,用来对磁盘的I/O操作进行合并,这就可能造成操作系统返回给内核写文件成功的地址和真实文件写到磁盘的地址是有差异的。为了区分这个差异,这里引入了2个变量,其中:
asyncXactLSN是一个XLogRecPtr类型的成员变量,表示最近需要异步提交的日志位置,并且也是由info_lck锁来保护。 在PostgreSQL中,日志记录刷写到磁盘有两种提交方式:同步和异步。其区别如下:
异步提交的提出主要是为了很多短事务(本身执行时间非常短)能立即提交。但是同时,也会打破WAL机制,造成数据库崩溃后数据丢失的危险。在PostgreSQL中,这个参数用户可以在连接中使用SET语句直接设置,实现异步日志提交和同步日志提交的切换,既减少数据丢失风险又能兼顾效率。 LogwrtResult表示当前已经写入系统缓冲区或者同步写入磁盘的日志位置,由info_lck 和 WALWriteLock 锁保护,结构如下: struct XLogwrtResult { XLogRecPtr Write; XLogRecPtr Flush; } XLogwrtResult; 可以看出,XLogwrtResult和XLogwrtRqst的结构相同,其原因也是为了区分是否真正写入到磁盘。 Xlblocks是一个XLogRecPtr *类型的成员变量,表示指向每个WAL缓存开始的LSN数组的首地址,可以根据这个变量加上日志缓存的偏移量就可以得到具体的对应LSN。 XLogCacheBlck是一个int类型的成员变量,表示缓冲区的大小,单位为块,系统会根据此块进行日志缓存Buffer的具体分配。 pages是一个char *类型的成员变量,表示指向日志缓存Buffer的首地址的指针,通过偏移量直接定位到哪块日志缓存Buffer,不过需要注意的是,内存日志缓存中存放的日志块,大小不固定,所以2个Buffer的大小可以不一致。 WAL缓存区可以理解为一个环形的共享缓存,每次缓存空间满后,会将头部的页面刷新到磁盘,同时写入新的页面,并将头部往后移动一位。具体来说,需要写入新的日志记录时:
因为WAL缓存区是顺序刷出的,这样日志文件中的信息必然是连续的。 为了更好的维护和管理WAL的刷盘,PostgreSQL提供辅助进程Walwriter 预发式日志写进程,Walwriter会周期性地将xlog日志块写入到磁盘。此时,刷新xlog日志页到磁盘的时机有以下几个:
其中,checkpoint会强制将所有数据缓存中的脏页刷新到磁盘,所以对应的xlog日志页必须在这之前刷新到磁盘。 Walwriter 预发式日志写进程数据库启动时,通过调用main()->PostmasterMain()->StartupDataBase()->WalWriterMain()启动Walwriter 辅助进程,Walwriter进程从Postmaster进程中startup子进程一结束就启动,一直保留到Postmaster进程命令其结束。 其中WalWriterMain()的具体步骤如下:
接下来,具体讲下核心函数XLogBackgroundFlush()。它主要是定位需要刷回磁盘的Xlog日志的开始位置和结束位置传递给XLogWrite()函数进行写日志操作,具体步骤如下:
XLogWrite函数的具体步骤如下:
除了Walwriter 预发式日志写进程之外,其他的进程如果满足刷新xlog日志页到磁盘的时机,也可以直接调用XLogWrite函数。 转载:http://mysql.taobao.org/monthly/2017/03/02/ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |