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先上個圖,看一下函數調用過程梗概,中間略過部分細節
初始化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"相關結構後在記憶體中的結構圖
初始化完twophase相關結構的記憶體結構圖
為了精簡上圖,把建立shmem的哈希表索引"ShmemIndex"時建立的HCTL結構删掉了,這個結構的作用是記錄建立可擴充哈希表的相關資訊。增加了左邊灰色底的部分,描述共享記憶體/shmem裡各變量實體布局概覽,由下往上,由低位址到高位址。其中的"twophase"相關結構圖在下面給出,要不上面的圖太大太複雜了。
twophase相關結構圖