国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

postgresql 內(nèi)核源碼分析 事務(wù)提交回滾狀態(tài)記錄 clog機(jī)制流程,commit log文件格式,事務(wù)狀態(tài)為什么單獨(dú)記錄的原因,分組優(yōu)化及l(fā)eader更新機(jī)制

這篇具有很好參考價(jià)值的文章主要介紹了postgresql 內(nèi)核源碼分析 事務(wù)提交回滾狀態(tài)記錄 clog機(jī)制流程,commit log文件格式,事務(wù)狀態(tài)為什么單獨(dú)記錄的原因,分組優(yōu)化及l(fā)eader更新機(jī)制。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

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è)方面:

  1. 理解工作原理:通過(guò)分析內(nèi)核源碼,可以深入理解PostgreSQL的工作原理,包括查詢優(yōu)化、事務(wù)管理、并發(fā)控制、存儲(chǔ)管理等方面的實(shí)現(xiàn)細(xì)節(jié)。
  2. 性能優(yōu)化:通過(guò)分析內(nèi)核源碼,可以找出性能瓶頸,進(jìn)行針對(duì)性的優(yōu)化,提高數(shù)據(jù)庫(kù)的性能和響應(yīng)速度。
  3. 定制開發(fā):通過(guò)分析內(nèi)核源碼,可以根據(jù)特定需求進(jìn)行定制開發(fā),例如實(shí)現(xiàn)新的數(shù)據(jù)類型、實(shí)現(xiàn)新的查詢優(yōu)化策略等。
  4. 安全性:通過(guò)分析內(nèi)核源碼,可以找出潛在的安全漏洞,進(jìn)行修復(fù)和加固,提高數(shù)據(jù)庫(kù)的安全性。
  5. 可靠性:通過(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í)。

注:未經(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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包