天天看點

PostgreSQL啟動過程中的那些事七:初始化共享記憶體和信号六:shmem中初始化twophase...

pg初始化shmem,給其加上索引"ShmemIndex"後,接着就在shmem裡初始化xlog。然後依次初始化clog、subtrans、twophase、multixact。安排按clog、subtrans、multixact、twophase的順序寫,把twophase放到multixact之後是因為前面三個用了相同的算法和資料結構,連起來寫可以加深印象和歸類記憶。這一篇讨論twophase的初始化。

分布式事務在pg裡用兩階段送出(twophase)支援,發起者給一個全局事務ID(GID)辨別該事務,同時涉及到的資料庫有一個本地事務,pg裡是prepare事務。

每一個全局事務(globaltransaction,簡寫為gxact)都和一個全局事務ID(GID)相關。每個用戶端用PREPARE TRANSACTION指令配置設定一個GID給一個postgres事務,做兩階段送出準備。

pg在共享記憶體數組裡保持所有活躍全局事務。當PREPARE TRANSACTION指令發起後,這個全局事務的GID被儲存在數組裡。這些發生在WAL日志記錄之前,因為如果已經有一個相同GID的已處于prepared狀态全局事務時,可以檢查重複GID和退出事務。

一個全局事務(gxact)還有一個輔助的PGPROC放在ProcArray數組裡。這友善了PGPROC勾住這個全局事務(gxact)的鎖。

為了從崩潰/關閉後再啟動時一切正常,所有準備好的事務必須存儲在永久存儲中。這包括鎖資訊,等待的通知(pending notifications)等。所有狀态資訊寫入到data/pg_towphase檔案夾裡的每事務狀态檔案。

1先上個圖,看一下函數調用過程梗概,中間略過部分細節

PostgreSQL啟動過程中的那些事七:初始化共享記憶體和信号六:shmem中初始化twophase...

初始化twophase方法調用流程圖

2初始化xlog相關結構

話說main()->…->PostmasterMain()->…->reset_shared() -> CreateSharedMemoryAndSemaphores()>…->TwoPhaseShmemInit(),初始化支援分布式事務的兩階段送出/TwoPhase相關資料結構TwoPhaseState等,用作記憶體裡管理和緩存兩階段送出/TwoPhase(對應于存放在"data/ pg_twophase"檔案夾裡的檔案)。

TwoPhaseShmemInit()->ShmemInitStruct(),在其中調用hash_search()在哈希表索引"ShmemIndex"中查找"Prepared Transaction Table",如果沒有,就在shmemIndex中給"Prepared Transaction Table"分一個HashElement和ShmemIndexEnt(entry),在其中的Entry中寫上"Prepared Transaction Table"。傳回ShmemInitStruct(),再調用ShmemAlloc()在共享記憶體上給"Prepared Transaction Table"相關結構(見下面“TwoPhase相關結構圖”)配置設定空間,設定entry(在這兒即ShmemIndexEnt類型變量)的成員location指向該空間,size成員記錄該空間大小,最後傳回ShmemInitStruct(),讓TwoPhaseStateData *類型靜态全局變量TwoPhaseState指向TwoPhaseStateData結構,TwoPhaseStateData的起始位址就是在shmem裡給"Prepared Transaction Table"相關結構配置設定的記憶體起始位址,設定其中TwoPhaseStateData結構類型的成員值。

相關變量、結構定義和初始化完成後資料結構圖在下面。

typedef struct TwoPhaseStateData

{

GlobalTransactionfreeGXacts;

int numPrepXacts;

GlobalTransactionprepXacts[1];

} TwoPhaseStateData;

static TwoPhaseStateData*TwoPhaseState;

typedef structGlobalTransactionData *GlobalTransaction;

typedef struct GlobalTransactionData

{

PGPROC proc;

TimestampTzprepared_at;

XLogRecPtr prepare_lsn;

Oid owner;

TransactionIdlocking_xid;

bool valid;

char gid[GIDSIZE];

} GlobalTransactionData;

struct PGPROC

{

SHM_QUEUE links;

PGSemaphoreData sem;

int waitStatus;

LocalTransactionIdlxid;

TransactionId xid;

TransactionId xmin;

int pid;

BackendId backendId;

Oid databaseId;

Oid roleId;

bool inCommit;

uint8 vacuumFlags;

bool recoveryConflictPending;

bool lwWaiting;

bool lwExclusive;

struct PGPROC*lwWaitLink;

LOCK *waitLock;

PROCLOCK *waitProcLock;

LOCKMODE waitLockMode;

LOCKMASK heldLocks;

Latch procLatch;

XLogRecPtr waitLSN;

int syncRepState;

SHM_QUEUE syncRepLinks;

SHM_QUEUE myProcLocks[NUM_LOCK_PARTITIONS];

struct XidCachesubxids;

};

typedef struct SHM_QUEUE

{

struct SHM_QUEUE *prev;

struct SHM_QUEUE *next;

} SHM_QUEUE;

關于上面的PGPROC結構這裡再說一下。

每一個背景程序(backend)在共享記憶體裡有一個PGPROC結構。還有一個目前未使用的PGPROC結構清單可以從中給新的backend配置設定。

Links:任何PGPROC在清單裡。當等待鎖時,PGPROC被連結到鎖的等待程序隊列裡(lock's waitProcs queue)。一個回收的PGPROC被連結到ProcGlobal的freeProcs清單裡。

pg的兩階段送出事務管理器還為每一個目前prepared事務建立一個假PGPROC結構。這些PGPROC結構出現在ProcArray資料結構以至于/目的是prepared事務顯示/看起來仍然在運作且正确的顯示其持有鎖。通過它的pid等于0這個事實,一個prepared事務PGPROC能夠和一個真正的程序在需要的時候能夠被差別。在一個prepared事務PGPROC裡的信号和lock-activity字段是不用的,但它的myProcLocks[]數組清單是有效的。

下面看看初始化完"Prepared Transaction Table"相關結構後在記憶體中的結構圖

PostgreSQL啟動過程中的那些事七:初始化共享記憶體和信号六:shmem中初始化twophase...

初始化完twophase相關結構的記憶體結構圖

為了精簡上圖,把建立shmem的哈希表索引"ShmemIndex"時建立的HCTL結構删掉了,這個結構的作用是記錄建立可擴充哈希表的相關資訊。增加了左邊灰色底的部分,描述共享記憶體/shmem裡各變量實體布局概覽,由下往上,由低位址到高位址。其中的"twophase"相關結構圖在下面給出,要不上面的圖太大太複雜了。

PostgreSQL啟動過程中的那些事七:初始化共享記憶體和信号六:shmem中初始化twophase...

twophase相關結構圖