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