PostgreSQL 事务日志WAL探秘(下篇) 原作者:何小栋(EthanHE) 创作时间:2019-01-03 23:00:42+08 |
发布于2019-01-03 23:00:42
![]() ![]() ![]() ![]() ![]() |
摘要
事务日志是数据库的重要组成部分,存储了数据库系统中所有更改和操作的历史,以确保数据库不会因为故障(例如掉电或其他导致服务器崩溃的故障)而丢失数据。在PostgreSQL(以下简称PG)中,事务日志文件称为Write Ahead Log(以下简称WAL)。
本文对PG中事务日志文件的结构进行了简要的剖析,内容包括WAL基本术语、WAL文件组成、WAL segment file内部结构和内容剖析、XLOG Record内存组织以及pg_waldump
工具简介。本篇是第二部分,内容包括WAL segment file内容剖析、XLOG Record内存组织以及pg_waldump
工具简介。
一、WAL segment file内容剖析
下面使用linux下的hexdump工具查看WAL文件中的内容,通过查看文件内容可以直观的观察上述的数据结构。
WAL文件信息
[xdb@localhost pg_wal]$ ll
total 32796
-rw-------. 1 xdb xdb 16777216 Dec 18 10:52 000000010000000100000042
...
以下使用000000010000000100000042文件为例进行解析。
1、XLogPageHeaderData
uint16 xlp_magic
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 0 -n 2
00000000 98 d0 |..|
00000002
magic value为0xD098。
注意:X86 CPU使用小端模式(Little-Endian),高位字节在内存高位地址,低位字节在内存低位地址。
uint16 xlp_info
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 2 -n 2
00000002 07 00 |..|
00000004
xlp_info
标志为0x0007,即XLP_FIRST_IS_CONTRECORD | XLP_LONG_HEADER | XLP_BKP_REMOVABLE
,表示:
- XLOG Record跨越page边界
- 这个page的header是XLogLongPageHeaderData
- 从该页起始的backup blocks是可选的(不一定存在)
TimeLineID(uint32) xlp_tli
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 4 -n 4
00000004 01 00 00 00 |....|
00000008
TimeLineID为0x00000001,即十进制数值1
XLogRecPtr(uint64) xlp_pageaddr
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 8 -n 8
00000008 00 00 00 42 01 00 00 00 |...B....|
00000010
XLog Record在事务日志指针(偏移)为0x00000001 42000000
uint32 xlp_rem_ln
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 16 -n 4
00000010 0f 00 00 00 |....|
00000014
上一页空间不足以存储XLOG Record,该Record在本页继续存储占用的空间大小:0x0000000F
2、XLogLongPageHeaderData
XLogLongPageHeaderData的第一个字段是XLogPageHeaderData,相关数据参见上述XLogPageHeaderData描述。
注意:XLogPageHeaderData结构体按最大基本类型(unit64)对齐,扩充为24Bytes(原为20Bytes,对齐为8 Bytes的倍数),因此XLogLongPageHeaderData的内容从偏移24处起算。
uint64 xlp_sysid
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 24 -n 8
00000018 42 72 7f 55 41 76 ee 5b |Br.UAv.[|
00000020
系统标识码0x5BEE7641557F7242
[xdb@localhost ~]$ echo $((0x5BEE7641557F7242))
6624362124887945794
使用pg_controldata
查看Database system identifier-->6624362124887945794
[xdb@localhost ~]$ pg_controldata
pg_control version number: 1100
Catalog version number: 201809051
Database system identifier: 6624362124887945794
...
uint32 xlp_seg_size
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 32 -n 4
00000020 00 00 00 01 |....|
00000024
值为0x01000000,即16M
[xdb@localhost ~]$ echo $((0x01000000))
16777216
uint32 xlp_xlog_blcksz
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 36 -n 4
00000024 00 20 00 00 |. ..|
00000028
值为0x00002000,即8K
[xdb@localhost ~]$ echo $((0x00002000))
8192
上一页XLOG Record的剩余部分
由于空间不足,上一page的XLOG Record在本页继续存储占用的空间(xlp_rem_len=0x0F
,补齐为16 Bytes)
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 40 -n 16
00000028 31 00 00 00 00 00 00 00 00 69 b8 40 25 00 00 00 |1........i.@%...|
00000038
3、XLogRecord
接下来是XLOG Record中的XLogRecord
uint32 xl_tot_len
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 56 -n 4
00000038 4f 00 00 00 |O...|
0000003c
XLOG Record长度为0x0000004F
TransactionId(uint32) xl_xid
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 60 -n 4
0000003c 6b 07 00 00 |k...|
00000040
事务ID为0x0000076B,即十进制的1899
XLogRecPtr(uint64) xl_pev
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 64 -n 8
00000040 c0 ff ff 41 01 00 00 00 |...A....|
00000048
上一个XLOG Record的偏移,即0x00000001 41FFFFC0
unit8 xl_info
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 72 -n 1
00000048 00 |.|
00000049
标志位为0x00
unit8 xl_rmid
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 73 -n 1
00000049 0a |.|
0000004a
该记录的资源管理器,即0x0A
2 bytes of padding
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 74 -n 2
0000004a 00 00 |..|
0000004c
pg_crc32c(uint32) xl_crc
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 76 -n 4
0000004c ea 21 d2 50 |.!.P|
00000050
CRC校验位,即0x50D221EA
4、XLOG Record data
XLogRecord之后是XLOG Record中的XLOG Record data
1) XLogRecordBlockHeader
uint8 id
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 80 -n 1
00000050 00 |.|
00000051
块引用ID为0x00,即0号Block.
uint8 fork_flags
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 81 -n 1
00000051 20 | |
00000052
值为0x20,高4位用于标记,即BKPBLOCK_HAS_DATA
uint16 data_length
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 82 -n 2
00000052 1e 00 |..|
00000054
payload bytes = 0x001E,十进制数值为30.
RelFileNode
接下来是RelFileNode,包括tablespace/database/relation,均为Oid类型(unsigned int)。
tablespace
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 84 -n 4
00000054 7f 06 00 00 |....|
00000058
值为0x0000067F,十进制值为1663
表空间为default
testdb=# select * from pg_tablespace where oid=1663;
spcname | spcowner | spcacl | spcoptions
------------+----------+--------+------------
pg_default | 10 | |
(1 row)
database
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 88 -n 4
00000058 12 40 00 00 |.@..|
0000005c
值为0x00004012,十进制值为16402,数据库为testdb
testdb=# select * from pg_database where oid=16402;
datname | datdba | encoding | datcollate | datctype | datistemplate | datallowconn | datconnlimit | datlastsysoid | datfroze
nxid | datminmxid | dattablespace | datacl
---------+--------+----------+------------+----------+---------------+--------------+--------------+---------------+---------
-----+------------+---------------+--------
testdb | 10 | 6 | C | C | f | t | -1 | 13284 |
561 | 1 | 1663 |
(1 row)
relation
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 92 -n 4
0000005c 56 42 00 00 |VB..|
00000060
值为0x00004256,十进制值为16982
testdb=# select oid,relfilenode,relname from pg_class where relfilenode = 16982;
oid | relfilenode | relname
-------+-------------+---------
16982 | 16982 | t_jfxx
(1 row)
相应的关系为t_jfxx
BlockNumber
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 96 -n 4
00000060 85 00 00 00 |....|
00000064
值为0x00000085,十进制值为133,这是对应的数据块号.
2) XLogRecordDataHeaderShort
接下来是XLogRecordDataHeaderShort/Long,由于数据小于256B,使用XLogRecordDataHeaderShort结构
unit8 id
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 100 -n 1
00000064 ff |.|
00000065
值为0xFF --> XLR_BLOCK_ID_DATA_SHORT 255
uint8 data_length
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 101 -n 1
00000065 03 |.|
00000066
值为0x03,3个字节,指的是main data的大小,3个字节是xl_heap_insert
结构体的大小.
3) block data
XLogRecordDataHeaderShort之后是block data,由两部分组成:
- xl_heap_header
- Tuple data
xl_heap_header
uint16 t_infomask2
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 102 -n 2
00000066 03 00 |..|
00000068
t_infomask2值为0x03,二进制值为00000000 00000011
uint16 t_infomask
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 104 -n 2
00000068 02 08 |..|
0000006a
t_infomask值为0x0802,二进制值为00001000 00000010
uint8 t_hoff
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 106 -n 1
0000006a 18 |.|
0000006b
t_hoff值(偏移)为0x18,十进制值为24
Tuple data
该部分的大小为25 Bytes:
XLOG Record的大小是0x4F即79 Bytes,减去XLogRecord(24 Bytes) + XLogRecordBlockHeader(20 Bytes) + XLogRecordDataHeaderShort(2 Bytes) + xl_heap_header(5 Bytes) + main data(3 Bytes)
,剩余空间大小为25 Bytes。
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 107 -n 25
0000006b 00 0d 32 30 39 31 39 0f 32 30 31 33 30 37 00 00 |..20919.201307..|
0000007b 00 00 00 00 00 00 03 b3 40 |........@|
00000084
4) main data
这部分存储的是xl_heap_insert
结构体
uint16 OffsetNumber
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 132 -n 2
00000084 26 00 |&.|
00000086
插入的tuple的偏移为0x0026,十进制为38
uint8 flags
[xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 134 -n 1
00000086 00 |.|
00000087
标志位为0x00
二、XLOG Record内存组织
在内存中,WAL Record通过XLogRecData结构体进行组织,形成一个链表。
下面插入一条记录,通过跟踪CopyXLogRecordToWAL函数,对该函数的输入参数rdata(XLogRecData *rdata
,该指针实际上指向&hdr_rdt
)进行分析。
-- 初始化脚本
drop table t_wal_longtext;
create table t_wal_longtext(c1 int not null,c2 varchar(3000),c3 varchar(3000),c4 varchar(3000));
insert into t_wal_longtext(c1,c2,c3,c4)
select i,rpad('C2-'||i,3000,'2'),rpad('C3-'||i,3000,'3'),rpad('C4-'||i,3000,'4')
from generate_series(1,7) as i;
-- 跟踪下面insert语句的执行
insert into t_wal_longtext(c1,c2,c3,c4) VALUES(8,'C2-8','C3-8','C4-8');
插入语句的XLOG Record,rdata由4部分组成:
- 第一部分:
XLogRecord + XLogRecordBlockHeader + XLogRecordDataHeaderShort
,共46(24 + 20 + 2)个字节。 - 第二部分:
xl_heap_header
,5个字节 - 第三部分:
tuple data
,20个字节 - 第四部分:
xl_heap_insert
,3个字节
下面是内存中的数据:
第一部分:
(gdb) p *rdata
$22 = {next = 0x244f2c0, data = 0x244f4c0 "J", len = 46}
(gdb) p *(XLogRecord *)rdata->data --> XLogRecord,24个字节
$27 = {xl_tot_len = 74, xl_xid = 2268, xl_prev = 5514538616, xl_info = 0 '\000', xl_rmid = 10 '\n', xl_crc = 1158677949}
(gdb) p *(XLogRecordBlockHeader *)(0x244f4c0+24) --> XLogRecordBlockHeader,20个字节
$29 = {id = 0 '\000', fork_flags = 32 ' ', data_length = 25}
(gdb) x/2bx (0x244f4c0+44) --> XLogRecordDataHeaderShort,2个字节
0x244f4ec: 0xff 0x03
第二部分:
(gdb) p *rdata->next
$23 = {next = 0x244f2d8, data = 0x7ffebea9d830 "\004", len = 5}
(gdb) p *(xl_heap_header *)rdata->next->data
$32 = {t_infomask2 = 4, t_infomask = 2050, t_hoff = 24 '\030'}
第三部分:
(gdb) p *rdata->next->next
$24 = {next = 0x244f2a8, data = 0x24e6a2f "", len = 20}
(gdb) x/20bc 0x24e6a2f
0x24e6a2f: 0 '\000' 8 '\b' 0 '\000' 0 '\000' 0 '\000' 11 '\v' 67 'C' 50 '2'
0x24e6a37: 45 '-' 56 '8' 11 '\v' 67 'C' 51 '3' 45 '-' 56 '8' 11 '\v'
0x24e6a3f: 67 'C' 52 '4' 45 '-' 56 '8'
(gdb)
第四部分:
(gdb) p *rdata->next->next->next
$25 = {next = 0x0, data = 0x7ffebea9d840 "\b", len = 3}
(gdb)
(gdb) p *(xl_heap_insert *)rdata->next->next->next->data
$33 = {offnum = 8, flags = 0 '\000'}
这个设计很赞,分离了数据组装和写入的职责,写入无需理会数据的结构组织,只需要按链表逐个写入其中的内容即可。
三、pg_waldump工具简介
按照上面几节对事务日志文件结构的介绍,可以自行写一个解析事务日志的小程序用于查看日志文件中的内容,PG已提供了dump事务日志的工具:pg_waldump
(PG 9.x或以下版本,使用pg_xlogdump
)。
在Linux下执行,使用--help查看帮助信息。
[xdb@localhost pg_wal]$ pg_waldump --help
pg_waldump decodes and displays PostgreSQL write-ahead logs for debugging.
Usage:
pg_waldump [OPTION]... [STARTSEG [ENDSEG]]
Options:
-b, --bkp-details output detailed information about backup blocks
…
各选项的详细解释可参加PG Document。
下面以举例的方式简单介绍该工具的使用。
[xdb@localhost pg_wal]$ ll
total 98332
-rw-------. 1 xdb xdb 16777216 Dec 20 12:02 000000010000000100000048
-rw-------. 1 xdb xdb 16777216 Dec 19 16:47 000000010000000100000049
-rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004A
-rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004B
-rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004C
-rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004D
drwx------. 2 xdb xdb 6 Nov 16 15:48 archive_status
以上为数据库服务器上的事务日志文件。
例一:查看000000010000000100000048最早的4个XLOG Record
命令:pg_waldump -p ./ -s 1/48000000 -n 4
[xdb@localhost pg_wal]$ pg_waldump -p ./ -s 1/48000000 -n 4
rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/48000070, prev 1/47FFFFF8, desc: INSERT off 117, blkref #0: rel 1663/16402/17028 blk 1110
rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/480000C0, prev 1/48000070, desc: INSERT off 7, blkref #0: rel 1663/16402/17031 blk 1111
rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/48000110, prev 1/480000C0, desc: INSERT off 8, blkref #0: rel 1663/16402/17031 blk 1111
rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/48000160, prev 1/48000110, desc: INSERT off 9, blkref #0: rel 1663/16402/17031 blk 1111
注意第一条记录,上一个LSN为1/47FFFFF8(prev 1/47FFFFF8),表示上一page最后一个XLOG Record存储在本页的XLogLongPageHeaderData中,存储的空间大小可以从该XLOG Record的LSN(1/48000070)和XLogLongPageHeaderData的大小(40 Bytes)推算而得。
例二:查看Redo point后的XLOG Record
首先使用pg_controldata
命令查看Redo point --> 1/484336A0
[xdb@localhost pg_wal]$ pg_controldata
pg_control version number: 1100
Catalog version number: 201809051
Database system identifier: 6624362124887945794
Database cluster state: in production
pg_control last modified: Thu 20 Dec 2018 12:17:39 PM CST
Latest checkpoint location: 1/484336D8
Latest checkpoint's REDO location: 1/484336A0
Latest checkpoint's REDO WAL file: 000000010000000100000048
Latest checkpoint's TimeLineID: 1
...
然后使用pg_waldump
查看
命令:pg_waldump -p ./ -s 1/484336A0
[xdb@localhost pg_wal]$ pg_waldump -p ./ -s 1/484336A0
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 1/484336A0, prev 1/48433668, desc: RUNNING_XACTS nextXid 1971 latestCompletedXid 1970 oldestRunningXid 1971
rmgr: XLOG len (rec/tot): 106/ 106, tx: 0, lsn: 1/484336D8, prev 1/484336A0, desc: CHECKPOINT_ONLINE redo 1/484336A0; tli 1; prev tli 1; fpw true; xid 0:1971; oid 17046; multi 1; offset 0; oldest xid 561 in DB 16402; oldest multi 1 in DB 16402; oldest/newest commit timestamp xid: 0/0; oldest running xid 1971; online
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 1/48433748, prev 1/484336D8, desc: RUNNING_XACTS nextXid 1971 latestCompletedXid 1970 oldestRunningXid 1971
pg_waldump: FATAL: error in WAL record at 1/48433748: invalid record length at 1/48433780: wanted 24, got 0
[xdb@localhost pg_wal]$
四、参考资料
- Write Ahead Logging — WAL:http://www.interdb.jp/pg/pgsql09.html
- PG Source Code:https://doxygen.postgresql.org
- WAL Internals Of PostgreSQL:https://www.pgcon.org/2012/schedule/attachments/258_212_Internals%20Of%20PostgreSQL%20Wal.pdf
- 关于结构体占用空间大小总结:https://blog.csdn.net/Netown_Ethereal/article/details/38898003
- PG 11 Document:https://www.postgresql.org/docs/11/pgwaldump.html
作者简介
- 作者:何小栋
- 网名:EthanHE
- 博客:https://www.jianshu.com/u/6b8fc3f18f72
- 个人简介:长期从事政务、金融等行业项目管理、产品研发和架构设计工作,具有丰富的政务、金融等行业知识,在多年的企业信息化建设中积累了大量的研发经验。对Oracle、PostgreSQL以及大数据等相关技术有深入研究。现就职于广州云图数据技术有限公司,任总架构师。
请在登录后发表评论,否则无法保存。