全页写的概述
在数据库发生一次checkpoint检查点后,需要往数据库的一个数据块里面插入数据,数据库在修改前需要把这个数据块从磁盘读到内存中数据缓冲区(shared buffer pool)里,然后再内存中进行数据块的修改插入。当我们执行insert语句时,对数据块进行插入数据A,内存中的数据块里面就会新增一条数据A。在commit提交后,PG数据块就会将这整个块写到WAL buffer日志缓冲区,然后再写到WAL日志文件中。然后我们再次对数据块进行插入数据B,内存中的数据缓冲区同样会再次新增一条数据B。在commit提交后,这个时候PG数据库就会将新插入数据B的事务日志条目写到WAL buffer日志缓冲区,最后再将这条数据库B的事务日志条目写到WAL日志文件中。
全页写就是把整个数据库块的内容写到WAL buffer日志缓冲区和WAL日志文件中。一个WAL记录长度是8字节,每个WAL段文件默认为16MB。一个WAL段可以记录将近200万事务。而如果存储8KB大小的数据块,只能储存2048个。就会导致WAL的写入量是非常大的。
全页写的特点
- 全页写的概念
将整个数据块写入到WAL日志文件中。 - 全页写的优点
提高数据库的安全性,解决块不一致问题。 - 全页写的缺点
导致WAL日志膨胀;
增加额外的磁盘I/O,影响数据库整体性能;
导致主备延迟变大。 - 全页写的控制
full_page_writes(默认on)。
全页写的模式
非强制模式
对于修改操作,当启用全页写时,pg会在每个检查点之后、每个页面第一次发生变更时,将头数据和整个页面作为一条WAL记录写入WAL缓冲区。
- 最近一次检查点之后,第一次修改的数据块会进行全页写,后续再修改时不会进行全页写,直到下一次检查点发生。
强制模式
对于备份操作,强制启用全页写,只要块发生变化,就会被整块写入WAL文件(不管是不是第一次,也不管有没有检查点)。因此,它写入的量是更大的。
- 当用pg_basebackup对数据库进行备份时,会自动执行强制模式,在备份期间被修改的数据块会全部写入WAL当中。
- pg_start_backup命令,对应函数do_pg_start_backup(xlog.c文件),其中开启强制全页写。
pg_stop_backup对应的函数do_pg_stop_backup,有一句关闭强制全页写。
因此手动执行pg_start_backup命令之后,备份完一定要执行pg_stop_backup,避免WAL暴增
建议数据库备份时间点选在业务空闲时间段进行。
Oracle full-page-writes
1、不提供full-page-writes开关控制。
2、以下备份发生时自动启动全页写。
alter tablespace xxx begin backup;
alter database begin backup;
块不一致的场景
对PostgreSQL来说,块不一致可以发生在两种场景:
- PG异常宕机(或者出现磁盘错误)时,数据文件中的页只写入了一部分。
- 使用操作系统命令备份正在运行的数据库,备份途中源数据库可能被修改,此时得到的备份数据状态就是不一致的
无论是崩溃恢复还是备份还原的恢复,都无法基于不一致的数据块进行。
块不一致的原因
- 操作系统进行I/O操作时,总是以块为单位,比如512字节、1KB等等。
- 数据库块一般是操作系统块的整数倍,比如2k、4k、8k等等。
- 块是数据库最小的I/O单位,当数据库写一个数据块时,操作系统需要I/O多次,可能在I/O过程中系统断电、磁盘故障等等原因导致一个数据块没有完整的写入,导致块不一致。
块不一致恢复
崩溃恢复
- 通过checksum发现“部分写”的数据页,并将wal中保存的这个完整数据页覆盖当前损坏的数据页,然后再继续redo恢复整个数据库。
备份恢复
- restore阶段,会直接还原不一致的块;但在recover阶段,会直接用WAL中一致的块对其进行覆盖,然后开始应用日志。
heap_xlog_insert
可以参考xlog的恢复代码
static void
heap_xlog_insert(XLogReaderState *record)
{
XLogRecPtr lsn = record->EndRecPtr;
xl_heap_insert *xlrec = (xl_heap_insert *) XLogRecGetData(record);
Buffer buffer;
Page page;
union
{
HeapTupleHeaderData hdr;
char data[MaxHeapTupleSize];
} tbuf;
HeapTupleHeader htup;
xl_heap_header xlhdr;
uint32 newlen;
Size freespace = 0;
RelFileNode target_node;
BlockNumber blkno;
ItemPointerData target_tid;
XLogRedoAction action;
XLogRecGetBlockTag(record, 0, &target_node, NULL, &blkno);
ItemPointerSetBlockNumber(&target_tid, blkno);
ItemPointerSetOffsetNumber(&target_tid, xlrec->offnum);
/*
* The visibility map may need to be fixed even if the heap page is
* already up-to-date.
*/
if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
{
Relation reln = CreateFakeRelcacheEntry(target_node);
Buffer vmbuffer = InvalidBuffer;
visibilitymap_pin(reln, blkno, &vmbuffer);
visibilitymap_clear(reln, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS);
ReleaseBuffer(vmbuffer);
FreeFakeRelcacheEntry(reln);
}
/*
* If we inserted the first and only tuple on the page, re-initialize the
* page from scratch.
*/
if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
{
buffer = XLogInitBufferForRedo(record, 0);
page = BufferGetPage(buffer);
PageInit(page, BufferGetPageSize(buffer), 0);
action = BLK_NEEDS_REDO;
}
else
action = XLogReadBufferForRedo(record, 0, &buffer);
if (action == BLK_NEEDS_REDO)
{
Size datalen;
char *data;
page = BufferGetPage(buffer);
if (PageGetMaxOffsetNumber(page) + 1 < xlrec->offnum)
elog(PANIC, "invalid max offset number");
data = XLogRecGetBlockData(record, 0, &datalen);
newlen = datalen - SizeOfHeapHeader;
Assert(datalen > SizeOfHeapHeader && newlen <= MaxHeapTupleSize);
memcpy((char *) &xlhdr, data, SizeOfHeapHeader);
data += SizeOfHeapHeader;
htup = &tbuf.hdr;
MemSet((char *) htup, 0, SizeofHeapTupleHeader);
/* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
memcpy((char *) htup + SizeofHeapTupleHeader,
data,
newlen);
newlen += SizeofHeapTupleHeader;
htup->t_infomask2 = xlhdr.t_infomask2;
htup->t_infomask = xlhdr.t_infomask;
htup->t_hoff = xlhdr.t_hoff;
HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
HeapTupleHeaderSetCmin(htup, FirstCommandId);
htup->t_ctid = target_tid;
if (PageAddItem(page, (Item) htup, newlen, xlrec->offnum,
true, true) == InvalidOffsetNumber)
elog(PANIC, "failed to add tuple");
freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */
PageSetLSN(page, lsn);
if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
PageClearAllVisible(page);
/* XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible */
if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)
PageSetAllVisible(page);
MarkBufferDirty(buffer);
}
if (BufferIsValid(buffer))
UnlockReleaseBuffer(buffer);
/*
* If the page is running low on free space, update the FSM as well.
* Arbitrarily, our definition of "low" is less than 20%. We can't do much
* better than that without knowing the fill-factor for the table.
*
* XXX: Don't do this if the page was restored from full page image. We
* don't bother to update the FSM in that case, it doesn't need to be
* totally accurate anyway.
*/
if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5)
XLogRecordPageWithFreeSpace(target_node, blkno, freespace);
}
/*
* Handles MULTI_INSERT record type.
*/
static void
参考
PostgreSQL技术内幕:事务处理深度探索
阿里云直播—pg-full-page机制与原理
PG技术大讲堂直播—PostgreSQL Full-Page Writes 全页写
标签:full,buffer,writes,数据库,全页,WAL,数据,page From: https://blog.51cto.com/u_13482808/7267286