POSTGRESQL 事務控制 -二- 事務開啓 -寫着費力- 看着費勁系列-

今天接着上回書, 事務如果在處理中沒有子事務, 則實現和控制是十分容易的, 但如果有子事務的情況下, 子事務通過 transactionState 結構體來實現,(上次已經提到了), 每一個 transactionState 都指向父事務的結構體的指針.

在繼續往下說之前, 我們的提到 clog, 這是理解後面要提到的一些事情的前提, 在 POSTGRESQL 中 clog 是記錄每一個事務相關的 xid, 以及事務的提交狀態, 狀態包含了 執行中, 已提交, 中斷, 子事務已提交. 那麼 clog 承擔了對整體事務狀態的記錄和查詢的任務. 

下面是一段 clog 代碼和 xidstatus 的狀態結構

從上面兩個圖中可以得到的信息

CLOG_BITS_PER_XACT  2  一個事務使用 2BIT

CLOG_XACTS_PER_BYTE 4  一個字節記錄 4 個事務

CLOG_XACTS_PER_PAGE (BLCKSZCLOG_XACTS_PER_BYTE)  一個頁面記錄 (81964=32784) 個事務

((xid)   (TransactionId)  CLOG_XACTS_PER_PAGE  

如果要尋找 xid 的頁面位置, 通過事務號除以一個頁面的記錄事務數, 就得到了具體這個事務所在的頁面數.

((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)

下面的計算可以獲得相關信息存儲在頁面中的第多少個字節

(TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)

最後的公式是計算事務信息存在在字節內的偏移量

 ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)

如果要知道 xid 事務的狀態, 則直接通過取餘的方式直接獲得事務的狀態頁面內的偏移位置

從上面的東西中可以獲得

1  事務的狀態是通過 2 個 BITS 來存儲的, 每個字節可以存儲 4 個事務的狀態

2  確認當前事務的狀態通過事務 ID , 事務 XID 是 32BIT

3  通過簡單的除法和商運算, 可以獲得事務的狀態信息

我們驗證一下, 我們開通 4 個 SESSION , 然後將 SESSION 中的事務 ID 打印出來.

分別事務 id 爲 623  - 626

下面四個計算的公式 

1 得到事務存儲的頁面 0 號頁面  

2 得到事務存儲在頁面總的偏移量

3 數據存儲在頁面內的多少字節

4 字節內的偏移量

通過事務 ID 就可以直接獲得事務的狀態在 CLOG 中的存儲位置, 並且提取相關的信息.

那麼我們來計算一下, 一個 8K 的頁面可以存儲多少事務, 以及事務的狀態

8K 頁面 * 8b/2b (事務的 4 個狀態) = 32KB = 16384 個事務的狀態

假設我們有 5MB 的內存空間, 可以存儲  10485760 個事務的狀態,這裏需要注意我們的事務動態的信息都是存在在內存中, 所以用更小的內存來控制更多的事務是有利於整體運行效率的.

現在我們迴歸主題, 子事務的與父事務的關係的存儲, 在我們的 pg 的數據庫目錄裏面有一個目錄是 pg_subtrans 目錄, 其中通過 pg_subtrans 這個目錄中的文件來存儲子事務與父事務之間的關係. 子事務保存自己上一次的事務, 相關事務的追述是通過自下而上的方式. 

下面是這段和 pg_subtrans 有關的代碼, 這段代碼在 xact.c 中, 這段代碼的主要的作用是在給子事務上的父事務 ID 進行記錄, 好進行自下而上的尋根

static void
AssignTransactionId(TransactionState s)
{

    bool        isSubXact = (s->parent != NULL);
    //是否是子事務,如果有父事務,則父事務不能爲空

    ResourceOwner currentOwner;
    bool        log_unknown_top = false;
    // 確認當前事務號有效並且事務是在inprogress 的狀態
    Assert(!TransactionIdIsValid(s->transactionId));
    Assert(s->state == TRANS_INPROGRESS);

    //如果當前事務在並行則不運行分配事務號

    if (IsInParallelMode() || IsParallelWorker())
        elog(ERROR, "cannot assign XIDs during a parallel operation");

在 subtrans.c 中包含關於在 PG 重啓後處理 pg_subtrans 中的數據的方法是直接清理掉這些數據, 一下這段代碼進行相關工作的代碼.

主要的原由是, 子事務是包含在事務內的, 在事務本身失效後, 這些子事務也沒有必要進行記錄, 所以在 pg_wal 中也不會有相關子事務的日誌記錄.

上期說到事務的 ID 只有在執行 INSERT ,UPDATE ,DELETE 的時候才進行事務號的分配, 那麼不分配事務號的情況下, 事務到底有沒有事務號, 實際上是有的在事務開始時是分配一個虛擬的事務 ID

在開始一個事務的時候, 會先開始分配內存和事務所需要的, buffer, 鎖等信息, 每個事務都需要有相關的 resourceOwner 信息, 虛擬的事務主要通過兩個方式來生成自己的虛擬事務 ID ,  benchend process  ID + 本地的計數器, 這樣就可以產生一個自己的臨時的虛擬的事務 ID 

在獲取了 ID 後, 我們直接就開始進行相關事務的開啓, 參加下面的語句

TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);

此時在設置完相關的變量的初始值後, 如事務就可以開始啓動了.

總結, 在一個事務開啓時

1  事務初始並沒有實際的事務 ID , 而是本地通過 backend 和計數器臨時分配的虛擬事務 ID , 只有在事務中出現 IUD 的操作纔會分配實際的事務 ID 

2  服務器在重啓或者 CRASH 後會清理與子事務與父事務相關的關聯的信息

3  在事務中, 存在越多的子事務, 則整體的系統的性能會越來越低, 所以不建議使用大量的 save point 產生大量的子事務. 並且子事務與父事務之間的關係是自下而上的搜索, 只有通過子事務才能查找到自己的父事務, 並在設計的時候, 通過簡單的事務 ID 與頁面數的餘數, 商可以直接快速定位事務的狀態.

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4ARjxf4WBfrLXJ_UgUDRyQ