clog 介紹
?專欄內(nèi)容:
- postgresql內(nèi)核源碼分析
- 手寫數(shù)據(jù)庫(kù)toadb
- 并發(fā)編程
?開源貢獻(xiàn):
- toadb開源庫(kù)
個(gè)人主頁(yè):我的主頁(yè)
管理社區(qū):開源數(shù)據(jù)庫(kù)
座右銘:天行健,君子以自強(qiáng)不息;地勢(shì)坤,君子以厚德載物.
前言
PostgreSQL是一種開源的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),其內(nèi)核源碼的分析對(duì)于深入理解其工作原理、性能優(yōu)化以及定制開發(fā)等方面都具有重要意義。
PostgreSQL的歷史可以追溯到1986年,當(dāng)時(shí)Michael Stonebraker和Eugene Wu在加州大學(xué)伯克利分校開始了POSTGRES項(xiàng)目的開發(fā)。該項(xiàng)目旨在開發(fā)一種具有可擴(kuò)展性和可靠性的關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng),以滿足日益增長(zhǎng)的數(shù)據(jù)庫(kù)應(yīng)用需求。在1994年,POSTGRES被發(fā)布為開源軟件,并更名為PostgreSQL。
PostgreSQL的特點(diǎn)包括支持ACID事務(wù)、支持全文搜索、支持存儲(chǔ)過(guò)程、支持觸發(fā)器、支持多版本并發(fā)控制(MVCC)等。此外,PostgreSQL還支持多種數(shù)據(jù)類型、支持多種平臺(tái)、支持多種編程語(yǔ)言接口等。
對(duì)于PostgreSQL內(nèi)核源碼的分析,其目的和意義主要體現(xiàn)在以下幾個(gè)方面:
- 理解工作原理:通過(guò)分析內(nèi)核源碼,可以深入理解PostgreSQL的工作原理,包括查詢優(yōu)化、事務(wù)管理、并發(fā)控制、存儲(chǔ)管理等方面的實(shí)現(xiàn)細(xì)節(jié)。
- 性能優(yōu)化:通過(guò)分析內(nèi)核源碼,可以找出性能瓶頸,進(jìn)行針對(duì)性的優(yōu)化,提高數(shù)據(jù)庫(kù)的性能和響應(yīng)速度。
- 定制開發(fā):通過(guò)分析內(nèi)核源碼,可以根據(jù)特定需求進(jìn)行定制開發(fā),例如實(shí)現(xiàn)新的數(shù)據(jù)類型、實(shí)現(xiàn)新的查詢優(yōu)化策略等。
- 安全性:通過(guò)分析內(nèi)核源碼,可以找出潛在的安全漏洞,進(jìn)行修復(fù)和加固,提高數(shù)據(jù)庫(kù)的安全性。
- 可靠性:通過(guò)分析內(nèi)核源碼,可以深入理解PostgreSQL的可靠性機(jī)制,例如備份與恢復(fù)、容錯(cuò)處理等方面的實(shí)現(xiàn)細(xì)節(jié)。
PostgreSQL內(nèi)核源碼的分析是一項(xiàng)重要的任務(wù),對(duì)于提高數(shù)據(jù)庫(kù)的性能、可靠性、安全性和定制開發(fā)能力都具有重要意義。
概述
postgresql數(shù)據(jù)庫(kù)中,將事務(wù)的狀態(tài)單獨(dú)存儲(chǔ)在文件中,也就是被稱為commit log的文件;文件位置clog目錄中,通常是在PostgreSQL數(shù)據(jù)庫(kù)安裝時(shí)創(chuàng)建的,其路徑可以在postgresql.conf配置文件中找到。默認(rèn)情況下,clog目錄通常位于PostgreSQL數(shù)據(jù)庫(kù)安裝目錄的“data_directory”參數(shù)所指定的目錄下,其命名可能因版本而異(例如,在PostgreSQL 16版本中,它被命名為“pg_xact”)。
事務(wù)狀態(tài)在數(shù)據(jù)庫(kù)運(yùn)行過(guò)程中,會(huì)被用來(lái)作為MVCC機(jī)制的一部分,判斷數(shù)據(jù)的可見性,所以是非常頻繁被讀取,同時(shí)在大量事務(wù)運(yùn)行時(shí),會(huì)有大量事務(wù)狀態(tài)的更新;為了高效的存儲(chǔ)和查詢,將事務(wù)狀態(tài)采用一種簡(jiǎn)潔的方式存儲(chǔ),可以很快加載到內(nèi)存,同時(shí)又能快速查找到對(duì)應(yīng)事務(wù)的狀態(tài),這種方式就非常關(guān)鍵。
本文將從以下幾方面進(jìn)行分享:
- clog文件格式
- clog緩存
- 事務(wù)狀態(tài)記錄
- clog的刷盤
- clog文件回收
- 并發(fā)控制
文件格式
用什么樣的文件格式來(lái)記錄事務(wù)狀態(tài)呢?
首先來(lái)看一下事務(wù)有那些狀態(tài),再分析文件格式如何組織。
事務(wù)狀態(tài)
對(duì)于每個(gè)xid對(duì)應(yīng)的狀態(tài),一般有運(yùn)行中running,提交commit,中止abort三種;而對(duì)于事務(wù)而言,還有子事務(wù)的存在,也就是嵌套事務(wù),子事務(wù)也同樣存在這三種狀態(tài);
這組合起來(lái)就多了,在postgresql中是這樣組織的,父子事務(wù)的關(guān)系,由另一個(gè)文件進(jìn)行記錄,而對(duì)于clog文件來(lái)講,只是記錄事務(wù)號(hào)與狀態(tài)信息;那這樣就簡(jiǎn)單了。
事務(wù)狀態(tài)分為四種:
- 運(yùn)行狀態(tài)
- 已經(jīng)提交
- 中止?fàn)顟B(tài)
- 子事務(wù)已提交
這里是不是又有點(diǎn)暈?zāi)兀?子事務(wù)只記錄提交狀態(tài),而沒有abort狀態(tài);
其實(shí)子事務(wù)的abort狀態(tài),并沒有區(qū)分子事務(wù)和父事務(wù),都共用abort狀態(tài);這里其實(shí)是postgresql做了一點(diǎn)優(yōu)化,因?yàn)橹挥凶邮聞?wù)的提交狀態(tài)時(shí),還需要再進(jìn)一步確認(rèn)父事務(wù)狀態(tài);而子事務(wù)的abort狀態(tài),直接就可以確定不可見;所以這里只對(duì)子事務(wù)的提交單獨(dú)加了狀態(tài),其它同普通事務(wù)一樣記錄狀態(tài)即可;
文件內(nèi)部格式
clog的文件結(jié)構(gòu)非常簡(jiǎn)單,每個(gè)文件以8KB為單位組成page;每個(gè)page中,以2個(gè)bit位為單位表示一個(gè)事務(wù)的狀態(tài);這樣一個(gè)字節(jié)可以表示4個(gè)事務(wù),按事務(wù)號(hào)0,1,… 順序存儲(chǔ)每個(gè)事務(wù)號(hào)的狀態(tài);
這樣的結(jié)構(gòu)對(duì)于修改和查找事務(wù)狀態(tài),就非常高效,只需要找到事務(wù)號(hào)對(duì)應(yīng)的偏移就可以了。
#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 除以 每個(gè)page有多少個(gè)事務(wù)號(hào);而對(duì)應(yīng)的狀態(tài)就是取余操作;
文件命名
在postgresql中事務(wù)號(hào)xid是32位的正整數(shù),不斷再循環(huán)使用,說(shuō)明事務(wù)號(hào)是有限的;
clog文件為了與緩存有映射關(guān)系,按緩存大小將事務(wù)號(hào)劃分到不同的文件段中,那么文件命名就按劃分后的順序來(lái)命名,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文件也是循環(huán)使用,根據(jù)事務(wù)號(hào)的回收,截?cái)嗖辉偈褂玫氖聞?wù)號(hào)狀態(tài);
clog緩存
事務(wù)狀態(tài)需要在數(shù)據(jù)庫(kù)服務(wù)重啟后還能使用,所以必須記錄在磁盤文件中,這就帶來(lái)一個(gè)問(wèn)題,讀寫磁盤效率非常低,常規(guī)辦法就是增加緩沖區(qū);
clog文件并不大,所以采用了SLRU(simple LRU)算法的緩沖區(qū),大小定義如下,單位為block,也就是page大??;
#define SLRU_PAGES_PER_SEGMENT 32
事務(wù)狀態(tài)記錄
事務(wù)號(hào)狀態(tài)寫入,先是計(jì)算對(duì)應(yīng)的page,然后看是否已經(jīng)加載到緩沖區(qū)中,如果沒有,則先加載到緩沖區(qū)中;
/* 獲取緩存區(qū)的序號(hào),如果不在緩存區(qū)中,這里會(huì)進(jìn)行加載 */
slotno = SimpleLruReadPage(XactCtl, pageno, XLogRecPtrIsInvalid(lsn), xid);
在緩沖區(qū)中找到對(duì)應(yīng)事務(wù)號(hào)的偏移,進(jìn)行位操作即可;
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;
}
}
緩存刷到磁盤
事務(wù)號(hào)的更新都是在緩沖區(qū)中進(jìn)行的,什么時(shí)候刷到磁盤呢?數(shù)據(jù)會(huì)丟失嗎?
對(duì)于有緩沖區(qū)設(shè)計(jì)的程序,我們總會(huì)提出這兩個(gè)問(wèn)題,下面我們來(lái)看postgresql中如何處理clog緩沖區(qū);
主要有以下幾個(gè)時(shí)機(jī),會(huì)進(jìn)行刷盤操作;
緩沖區(qū)置換
當(dāng)緩沖區(qū)都滿時(shí),又需要加載一個(gè)page時(shí),就需要置換一個(gè)緩沖區(qū)出去,此時(shí)如果為臟時(shí),就需要刷盤;
刷盤時(shí)調(diào)用如下接口進(jìn)行;
static void SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata);
checkpoint時(shí)
在checkpoint時(shí),會(huì)將所有緩存區(qū)刷到磁盤上,調(diào)用以下接口,內(nèi)部也是按page進(jìn)行刷盤;
void
SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied);
服務(wù)啟動(dòng)、停止時(shí)
同checkpoint一樣,將所有緩存區(qū)刷盤;
事務(wù)狀態(tài)數(shù)據(jù),并沒有隨著事務(wù)提交一起刷盤,可能會(huì)有丟失的情況,如果這種情況發(fā)生,也會(huì)存在redo,此時(shí)可以從WAL恢復(fù)事務(wù)狀態(tài),所以數(shù)據(jù)完整性和一致性是得到保障的;
回收clog段文件
隨著事務(wù)號(hào)的增加和回卷,有些clog段文件就不再需要,需要進(jìn)行刪除回收;
clog模塊通過(guò)刪除和truncate操作來(lái)進(jìn)行回收段文件;
truncate段文件
在進(jìn)行checkpoint時(shí),會(huì)根據(jù)事務(wù)號(hào)進(jìn)行判斷段文件是否有效,在目錄下查找無(wú)效的段文件進(jìn)行truncate;
另外在vacuum時(shí),會(huì)更新frozenxid,那么更新后,就會(huì)有一些事務(wù)號(hào)不再使用,也會(huì)對(duì)整個(gè)目錄中的段文件進(jìn)行truncate;
void
SimpleLruTruncate(SlruCtl ctl, int cutoffPage);
當(dāng)然這里的truncate,不是對(duì)文件的truncate,而是對(duì)目錄中段文件,基實(shí)是對(duì)無(wú)效段文件的刪除;
truncate時(shí)也會(huì)記錄一條WAL日志,調(diào)用以下接口;
static void
WriteTruncateXlogRec(int pageno, TransactionId oldestXact, Oid oldestXactDb);
刪除段文件
當(dāng)清理multiXact時(shí),如果存在較早的clog,就會(huì)將它們刪除;或者上面進(jìn)行trancate時(shí),會(huì)批量將舊的clog進(jìn)行刪除;
void
SlruDeleteSegment(SlruCtl ctl, int segno);
并發(fā)控制
無(wú)論是對(duì)于共享緩存區(qū),還是對(duì)于clog文件的操作,都會(huì)存在多任務(wù)同時(shí)訪問(wèn),所以需要一定的并發(fā)控制;
對(duì)于緩沖區(qū)SLRU,會(huì)有一個(gè)總的controllock,每次需要修改共享緩沖區(qū)時(shí),都需要先加此鎖;
而對(duì)于緩沖區(qū)塊的操作,每一個(gè)緩沖區(qū)塊會(huì)有一個(gè)獨(dú)立的鎖,讀寫緩沖區(qū)塊時(shí)獲得此鎖即可;
LRU共享內(nèi)存鎖
對(duì)于SLRU結(jié)構(gòu)的內(nèi)容的修改,或者緩沖區(qū)替換,段文件操作等,都必須先獲取此鎖,它在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);
## 緩沖區(qū)塊鎖
對(duì)于每個(gè)緩沖區(qū)塊buffer的操作,每個(gè)buffer都定義了一把鎖 buffer_locks;
```c
/* Initialize LWLocks */
shared->buffer_locks = (LWLockPadded *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(LWLockPadded));
為了避免死鎖,它的獲取前,必須先要加上LRU共享控制鎖,然后再獲取,獲取到時(shí),就可以釋放LRU控制鎖;
如下所示:
LWLockRelease(shared->ControlLock);
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_SHARED);
LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
LWLockRelease(&shared->buffer_locks[slotno].lock);
寫操作
而對(duì)于只讀操作時(shí),只需要加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;
}
對(duì)于大并發(fā)下的事務(wù)狀態(tài)更新,postgresql 還進(jìn)行了精細(xì)的優(yōu)化,避免controlLock競(jìng)爭(zhēng),增加group的優(yōu)化,對(duì)于在同一個(gè)clog page中的事務(wù)號(hào)更新,只由第一個(gè)backend寫入clog,其它只是加到grouplist中,利用proc記錄進(jìn)行優(yōu)化;
讀操作
此處獲取事務(wù)狀態(tài),只讀操作,如果緩沖區(qū)中已經(jīng)加載了事務(wù)號(hào)所在的page,此時(shí)只加controllock的共享模式;
結(jié)尾
非常感謝大家的支持,在瀏覽的同時(shí)別忘了留下您寶貴的評(píng)論,如果覺得值得鼓勵(lì),請(qǐng)點(diǎn)贊,收藏,我會(huì)更加努力!
作者郵箱:study@senllang.onaliyun.com
如有錯(cuò)誤或者疏漏歡迎指出,互相學(xué)習(xí)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-721438.html
注:未經(jīng)同意,不得轉(zhuǎn)載!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-721438.html
到了這里,關(guān)于postgresql 內(nèi)核源碼分析 事務(wù)提交回滾狀態(tài)記錄 clog機(jī)制流程,commit log文件格式,事務(wù)狀態(tài)為什么單獨(dú)記錄的原因,分組優(yōu)化及l(fā)eader更新機(jī)制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!