PostgreSQL嵌套事務提交流程研究

PostgreSQL嵌套事務提交流程研究

父事務最終提交代碼流程圖

在這裏插入圖片描述

事務提交調用流程

其中值得拿出來講的,主要是TransactionIdSetTreeStatus這個方法。

這裏涉及到一個概念,子事務。在PG這個地方,子事務的概念主要指:事務從開始到結束,期間可以

savepoint,之後rollback到savepoint而不是事務起點,在實際情況中多有應用,因此這裏父事務與子事務(比如事務最終提交,但期間有回滾的情況,或者事務期間多次savepoint)必須儘可能原子性的方式寫入,否則事務可見性就會出現問題。

在代碼註釋裏面,對這裏的寫入做了一個比較直觀的例子:

比如一個事務t,有子事務
t1,t2,t3,t4,其中t,t1被映射到clog頁p1,t2和t3在p2,t4在頁p3。那麼寫入的時候,順序如下:

  • 設置p2 的t2 t3爲子提交,之後設置p3的t4位子提交

  • 設置t1爲子提交,之後設置t爲已提交,之後設置t1爲已提交

  • 設置 t2 t3 爲已提交,設置t4位已提交

對於回滾,實際上也是調用TransactionIdSetTreeStatus方法,只是上層函數是TransactionIdAbortTree,設置的標記是TRANSACTIONSTATUSABORTED,也就是記錄事務爲中斷。語義上來說,對於事務中斷,由於事務的原子性要求,中斷的事務數據就是不可見的了,沒啥問題。

TransactionIdSetTreeStatus()講解

代碼註釋

在提交日誌中記錄事務條目的最終狀態

事務及其子事務樹。注意確保這是

高效且儘可能原子。

xid是用於設置狀態的單個xid。通常是

頂級提交或中止的頂級transactionid。它可以

當我們記錄事務中止時,它也是子事務。

subxids是長度爲nsubxids的xid數組,表示子事務

在xid樹中。在各種情況下,nsubxids可以爲零。

記錄異步時,lsn必須是提交記錄的WAL位置

提交。對於同步提交,它可以是InvalidXLogRecPtr,因爲

調用者保證在這種情況下已經清除了提交記錄。它

對於中止情況,也應爲InvalidXLogRecPtr。

在提交的情況下,原子性受是否所有子軸都在

與xid相同的CLOG頁面。如果全部都是,那麼鎖將被抓住

僅一次,狀態將設置爲直接提交。除此以外

我們必須

1.設置子提交的所有與子目錄不在同一頁面上的子目錄

主要的xid

2.在同一頁面上自動設置提交的主xid和子xid

3.再次遍歷第一束並將其設置爲已提交

請注意,就併發檢查程序而言,主要交易

整個提交仍然是原子的。

示例:

TransactionId t提交併具有子下標t1,t2,t3,t4

t在頁面p1上,t1也在p1上,t2和t3在p2上,t4在p3上

1.更新第2-3頁:

第2頁:將t2,t3設置爲子提交

第3頁:將t4設爲子提交

2.更新第1頁:

將t1設爲子提交,

然後將t設置爲commit,

然後將t1設置爲commit

3.更新第2-3頁:

第2頁:將t2,t3設置爲已提交

第3頁:將t4設置爲已提交

注意:這是一個低級例程,不是首選入口點

用於大多數用途; transam.c中的函數是預期的調用方。

XXX考慮在需要的頁面上發佈FADVISE_WILLNEED,

但尚未緩存,並提示頁面不會掉出

尚未緩存。

分析

這裏涉及到一個概念:子事務。在PG這個地方,子事務的概念主要指:事務從開始到結束,期間可以savepoint,之後rollback到savepoint而不是事務起點,在實際情況中多有應用,因此這裏父事務與子事務(比如事務最終提交,但期間有回滾的情況,或者事務期間多次savepoint)必須儘可能原子性的方式寫入,否則事務可見性就會出現問題。

TransactionIdSetTreeStatus的代碼註釋中,對這裏的寫入做了一個比較直觀的例子:

比如一個事務t,有子事務
t1,t2,t3,t4,其中t,t1被映射到clog頁p1,t2和t3在p2,t4在頁p3。那麼寫入的時候,順序如下:

  • 設置p2 的t2 t3爲子提交,之後設置p3的t4位子提交

  • 設置t1爲子提交,之後設置t爲已提交,之後設置t1爲已提交

  • 設置 t2 t3 爲已提交,設置t4位已提交

對於回滾,實際上也是調用TransactionIdSetTreeStatus方法,只是上層函數是

TransactionIdAbortTree,設置的標記是TRANSACTIONSTATUSABORTED,也就是記錄事務爲中斷。語義上來說,對於事務中斷,由於事務的原子性要求,中斷的事務數據就是不可見的了,沒啥問題。

在這裏插入圖片描述

子事務subtrans?

當我們使用savepoint時,會產生子事務,子事務和父事務一樣,可能消耗XID。一旦爲子事務分配了XID,那麼就涉及CLOG的原子操作了。因爲要保證父事務和所有的子事務的CLOG一致性。

當不消耗XID時,需要通過SubTransactionId來區分子事務。

參考: src/backend/access/transam/README

《Transaction and Subtransaction Numbering》

事務和子事務都可以有XID,子事務和事務一樣,在真正需要XID的時候纔會分配XID,

也就是說,一個事務,如果它有子事務,可能消耗多個XID。

另外需要注意,如果子事務要分配XID,必須先給它的父事務分配一個XID,才能給子事務分配XID,因爲要確保子事務的XID是在父事務後分配的。

README原文

Transaction and Subtransaction Numbering

----------------------------------------

Transactions and subtransactions are assigned permanent XIDs only when/if

they first do something that requires one — typically, insert/update/delete

a tuple, though there are a few other places that need an XID assigned.

If a subtransaction requires an XID, we always first assign one to its

parent. This maintains the invariant that child transactions have XIDs later

than their parents, which is assumed in a number of places.

The subsidiary actions of obtaining a lock on the XID and entering it into

ux_subtrans and UX_PROC are done at the time it is assigned.

A transaction that has no XID still needs to be identified for various

purposes, notably holding locks. For this purpose we assign a "virtual

transaction ID" or VXID to each top-level transaction. VXIDs are formed from

two fields, the backendID and a backend-local counter; this arrangement allows

assignment of a new VXID at transaction start without any contention for

shared memory. To ensure that a VXID isn’t re-used too soon after backend

exit, we store the last local counter value into shared memory at backend

exit, and initialize it from the previous value for the same backendID slot

at backend start. All these counters go back to zero at shared memory

re-initialization, but that’s OK because VXIDs never appear anywhere on-disk.

Internally, a backend needs a way to identify subtransactions whether or not

they have XIDs; but this need only lasts as long as the parent top transaction

endures. Therefore, we have SubTransactionId, which is somewhat like

CommandId in that it’s generated from a counter that we reset at the start of

each top transaction. The top-level transaction itself has SubTransactionId 1,

and subtransactions have IDs 2 and up. (Zero is reserved for

InvalidSubTransactionId.) Note that subtransactions do not have their

own VXIDs; they use the parent top transaction’s VXID.

子事務日誌

嵌套事務形成了一個事務樹,因此只需要通過指定事務,在子事務日誌中逐級向上回溯尋找其父事務,直到遇到一個事務的父事務id爲無效事務id,則說明該事務爲所要尋找的根事務id。

subtrans日誌的健壯性要求和clog日誌是完全不同的,因爲需要記錄的只是當前打開事務的子事務信息。所以在系統奔潰或重啓時並不保存數據。由於在系統奔潰時不需要保存數據,因此也不需要和xlog進行交互,也沒有對應的redo函數。在數據庫啓動時,只要使當前活躍的子事務頁面爲全0就可以了。

分析

父子關係的嵌套事務日誌,即: ux_subtrans 日誌

在Clog同步的時候,通過將父子事務以原子化的方式,一併修改是否已提交的狀態

有關事務的父子嵌套關係,其他節點只需要瞭解其是否最終提交,忽略其父子關係

子事務的提交狀態分爲”sub-commited”和”最終commited”,即其他節點只需瞭解所要xid(包括sub-xid)的提交狀態爲“最終committed”,而忽略“sub-commited”。

參考

PostgreSQL的clog—從事務回滾速度談起

https://ssl.zzidc.com/chanpinzixun/2019/0801/654.html

pg_clog的原子操作與pg_subtrans(子事務)

https://blog.csdn.net/postgrechina/article/details/49130709

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章