怎么在天山建设云网站备案,编程网站排名,wordpress 去掉超链接,免费ppt模板百度云资源clog 介绍 专栏内容#xff1a; postgresql内核源码分析手写数据库toadb并发编程 开源贡献#xff1a; toadb开源库 个人主页#xff1a;我的主页 管理社区#xff1a;开源数据库 座右铭#xff1a;天行健#xff0c;君子以自强不息#xff1b;地势坤#xff0c;君…clog 介绍 专栏内容 postgresql内核源码分析手写数据库toadb并发编程 开源贡献 toadb开源库 个人主页我的主页 管理社区开源数据库 座右铭天行健君子以自强不息地势坤君子以厚德载物. 文章目录 clog 介绍前言概述文件格式事务状态文件内部格式文件命名 clog缓存事务状态记录缓存刷到磁盘缓冲区置换checkpoint时服务启动、停止时 回收clog段文件truncate段文件删除段文件 并发控制LRU共享内存锁写操作读操作 结尾 前言
PostgreSQL是一种开源的关系型数据库管理系统其内核源码的分析对于深入理解其工作原理、性能优化以及定制开发等方面都具有重要意义。
PostgreSQL的历史可以追溯到1986年当时Michael Stonebraker和Eugene Wu在加州大学伯克利分校开始了POSTGRES项目的开发。该项目旨在开发一种具有可扩展性和可靠性的关系型数据库管理系统以满足日益增长的数据库应用需求。在1994年POSTGRES被发布为开源软件并更名为PostgreSQL。
PostgreSQL的特点包括支持ACID事务、支持全文搜索、支持存储过程、支持触发器、支持多版本并发控制MVCC等。此外PostgreSQL还支持多种数据类型、支持多种平台、支持多种编程语言接口等。
对于PostgreSQL内核源码的分析其目的和意义主要体现在以下几个方面
理解工作原理通过分析内核源码可以深入理解PostgreSQL的工作原理包括查询优化、事务管理、并发控制、存储管理等方面的实现细节。性能优化通过分析内核源码可以找出性能瓶颈进行针对性的优化提高数据库的性能和响应速度。定制开发通过分析内核源码可以根据特定需求进行定制开发例如实现新的数据类型、实现新的查询优化策略等。安全性通过分析内核源码可以找出潜在的安全漏洞进行修复和加固提高数据库的安全性。可靠性通过分析内核源码可以深入理解PostgreSQL的可靠性机制例如备份与恢复、容错处理等方面的实现细节。
PostgreSQL内核源码的分析是一项重要的任务对于提高数据库的性能、可靠性、安全性和定制开发能力都具有重要意义。
概述
postgresql数据库中将事务的状态单独存储在文件中也就是被称为commit log的文件文件位置clog目录中通常是在PostgreSQL数据库安装时创建的其路径可以在postgresql.conf配置文件中找到。默认情况下clog目录通常位于PostgreSQL数据库安装目录的“data_directory”参数所指定的目录下其命名可能因版本而异例如在PostgreSQL 16版本中它被命名为“pg_xact”。
事务状态在数据库运行过程中会被用来作为MVCC机制的一部分判断数据的可见性所以是非常频繁被读取同时在大量事务运行时会有大量事务状态的更新为了高效的存储和查询将事务状态采用一种简洁的方式存储可以很快加载到内存同时又能快速查找到对应事务的状态这种方式就非常关键。
本文将从以下几方面进行分享
clog文件格式clog缓存事务状态记录clog的刷盘clog文件回收并发控制
文件格式
用什么样的文件格式来记录事务状态呢 首先来看一下事务有那些状态再分析文件格式如何组织。
事务状态
对于每个xid对应的状态一般有运行中running提交commit中止abort三种而对于事务而言还有子事务的存在也就是嵌套事务子事务也同样存在这三种状态
这组合起来就多了在postgresql中是这样组织的父子事务的关系由另一个文件进行记录而对于clog文件来讲只是记录事务号与状态信息那这样就简单了。
事务状态分为四种
运行状态已经提交中止状态子事务已提交
这里是不是又有点晕呢 子事务只记录提交状态而没有abort状态 其实子事务的abort状态并没有区分子事务和父事务都共用abort状态这里其实是postgresql做了一点优化因为只有子事务的提交状态时还需要再进一步确认父事务状态而子事务的abort状态直接就可以确定不可见所以这里只对子事务的提交单独加了状态其它同普通事务一样记录状态即可
文件内部格式
clog的文件结构非常简单每个文件以8KB为单位组成page每个page中以2个bit位为单位表示一个事务的状态这样一个字节可以表示4个事务按事务号01… 顺序存储每个事务号的状态
这样的结构对于修改和查找事务状态就非常高效只需要找到事务号对应的偏移就可以了。
#define TransactionIdToPage(xid) ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToByte(xid) (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
#define TransactionIdToBIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)可以看到page就是 xid 除以 每个page有多少个事务号而对应的状态就是取余操作
文件命名
在postgresql中事务号xid是32位的正整数不断再循环使用说明事务号是有限的
clog文件为了与缓存有映射关系按缓存大小将事务号划分到不同的文件段中那么文件命名就按划分后的顺序来命名0001,0002依次
取值算法如下
0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT #define SlruFileName(ctl, path, seg) \snprintf(path, MAXPGPATH, %s/%04X, (ctl)-Dir, seg)可以看到clog文件也是循环使用根据事务号的回收截断不再使用的事务号状态
clog缓存
事务状态需要在数据库服务重启后还能使用所以必须记录在磁盘文件中这就带来一个问题读写磁盘效率非常低常规办法就是增加缓冲区
clog文件并不大所以采用了SLRU(simple LRU)算法的缓冲区大小定义如下,单位为block也就是page大小
#define SLRU_PAGES_PER_SEGMENT 32事务状态记录
事务号状态写入先是计算对应的page然后看是否已经加载到缓冲区中如果没有则先加载到缓冲区中 /* 获取缓存区的序号如果不在缓存区中这里会进行加载 */slotno SimpleLruReadPage(XactCtl, pageno, XLogRecPtrIsInvalid(lsn), xid);在缓冲区中找到对应事务号的偏移进行位操作即可
static void
TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
{int byteno TransactionIdToByte(xid);int bshift TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;char *byteptr;char byteval;char curval;byteptr XactCtl-shared-page_buffer[slotno] byteno;curval (*byteptr bshift) CLOG_XACT_BITMASK;if (InRecovery status TRANSACTION_STATUS_SUB_COMMITTED curval TRANSACTION_STATUS_COMMITTED)return;byteval *byteptr;byteval ~(((1 CLOG_BITS_PER_XACT) - 1) bshift);byteval | (status bshift);*byteptr byteval;if (!XLogRecPtrIsInvalid(lsn)){int lsnindex GetLSNIndex(slotno, xid);if (XactCtl-shared-group_lsn[lsnindex] lsn)XactCtl-shared-group_lsn[lsnindex] lsn;}
}缓存刷到磁盘
事务号的更新都是在缓冲区中进行的什么时候刷到磁盘呢数据会丢失吗 对于有缓冲区设计的程序我们总会提出这两个问题下面我们来看postgresql中如何处理clog缓冲区
主要有以下几个时机会进行刷盘操作;
缓冲区置换
当缓冲区都满时又需要加载一个page时就需要置换一个缓冲区出去此时如果为脏时就需要刷盘 刷盘时调用如下接口进行
static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata);checkpoint时
在checkpoint时会将所有缓存区刷到磁盘上调用以下接口内部也是按page进行刷盘
void
SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied);服务启动、停止时
同checkpoint一样将所有缓存区刷盘
事务状态数据并没有随着事务提交一起刷盘可能会有丢失的情况如果这种情况发生也会存在redo此时可以从WAL恢复事务状态所以数据完整性和一致性是得到保障的
回收clog段文件
随着事务号的增加和回卷有些clog段文件就不再需要需要进行删除回收
clog模块通过删除和truncate操作来进行回收段文件
truncate段文件
在进行checkpoint时会根据事务号进行判断段文件是否有效在目录下查找无效的段文件进行truncate; 另外在vacuum时会更新frozenxid那么更新后就会有一些事务号不再使用也会对整个目录中的段文件进行truncate
void
SimpleLruTruncate(SlruCtl ctl, int cutoffPage);当然这里的truncate不是对文件的truncate而是对目录中段文件基实是对无效段文件的删除
truncate时也会记录一条WAL日志调用以下接口
static void
WriteTruncateXlogRec(int pageno, TransactionId oldestXact, Oid oldestXactDb);删除段文件
当清理multiXact时如果存在较早的clog就会将它们删除或者上面进行trancate时会批量将旧的clog进行删除
void
SlruDeleteSegment(SlruCtl ctl, int segno);并发控制
无论是对于共享缓存区还是对于clog文件的操作都会存在多任务同时访问所以需要一定的并发控制
对于缓冲区SLRU会有一个总的controllock每次需要修改共享缓冲区时都需要先加此锁
而对于缓冲区块的操作每一个缓冲区块会有一个独立的锁读写缓冲区块时获得此锁即可
LRU共享内存锁
对于SLRU结构的内容的修改或者缓冲区替换段文件操作等都必须先获取此锁它在SlruSharedData中定义
c
typedef struct SlruSharedData
{LWLock *ControlLock;bool *page_dirty;int *page_number;int *page_lru_count;LWLockPadded *buffer_locks;} SlruSharedData;LWLockAcquire(shared-ControlLock, LW_EXCLUSIVE); ## 缓冲区块锁
对于每个缓冲区块buffer的操作每个buffer都定义了一把锁 buffer_locksc/* Initialize LWLocks */shared-buffer_locks (LWLockPadded *) (ptr offset);offset MAXALIGN(nslots * sizeof(LWLockPadded));为了避免死锁它的获取前必须先要加上LRU共享控制锁然后再获取获取到时就可以释放LRU控制锁
如下所示 LWLockRelease(shared-ControlLock);LWLockAcquire(shared-buffer_locks[slotno].lock, LW_SHARED);LWLockAcquire(shared-ControlLock, LW_EXCLUSIVE);LWLockRelease(shared-buffer_locks[slotno].lock);写操作
而对于只读操作时只需要加ControlLock的share锁即可
int
SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno, TransactionId xid)
{SlruShared shared ctl-shared;int slotno;/* Try to find the page while holding only shared lock */LWLockAcquire(shared-ControlLock, LW_SHARED);/* See if page is already in a buffer */for (slotno 0; slotno shared-num_slots; slotno){if (shared-page_number[slotno] pageno shared-page_status[slotno] ! SLRU_PAGE_EMPTY shared-page_status[slotno] ! SLRU_PAGE_READ_IN_PROGRESS){/* See comments for SlruRecentlyUsed macro */SlruRecentlyUsed(shared, slotno);/* update the stats counter of pages found in the SLRU */pgstat_count_slru_page_hit(shared-slru_stats_idx);return slotno;}}/* No luck, so switch to normal exclusive lock and do regular read */LWLockRelease(shared-ControlLock);LWLockAcquire(shared-ControlLock, LW_EXCLUSIVE);return SimpleLruReadPage(ctl, pageno, true, xid);
}XidStatus
TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
{int pageno TransactionIdToPage(xid);int byteno TransactionIdToByte(xid);int bshift TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;int slotno;int lsnindex;char *byteptr;XidStatus status;/* lock is acquired by SimpleLruReadPage_ReadOnly */slotno SimpleLruReadPage_ReadOnly(XactCtl, pageno, xid);byteptr XactCtl-shared-page_buffer[slotno] byteno;status (*byteptr bshift) CLOG_XACT_BITMASK;lsnindex GetLSNIndex(slotno, xid);*lsn XactCtl-shared-group_lsn[lsnindex];LWLockRelease(XactSLRULock);return status;
}对于大并发下的事务状态更新postgresql 还进行了精细的优化避免controlLock竞争增加group的优化对于在同一个clog page中的事务号更新只由第一个backend写入clog其它只是加到grouplist中利用proc记录进行优化
读操作
此处获取事务状态只读操作如果缓冲区中已经加载了事务号所在的page此时只加controllock的共享模式
结尾 非常感谢大家的支持在浏览的同时别忘了留下您宝贵的评论如果觉得值得鼓励请点赞收藏我会更加努力 作者邮箱studysenllang.onaliyun.com 如有错误或者疏漏欢迎指出互相学习。
注未经同意不得转载