同樣還是這張圖,之前發過shared_buffer和os cache、wal buffer和work mem的文章,今天的主題是圖中的clog,即 commit log,PostgreSQL10之前放在數據庫目錄的pg_clog下面。PostgreSQL10之后修更名為xact,數據目錄變更為pg_xact下面,表現形式是一些物理文件。
PostgreSQL為什么要使用clog呢,眾所周知,PostgreSQL有著獨特的MVCC機制,由于其多版本的特性,
在進行可見性判斷時,需要獲取事務的狀態,即元組中 t_xmin 和 t_xmax 的狀態,需要clog來記錄事務的狀態,從而判斷其可見性,內存里的訪問遠遠快于磁盤讀寫,因此PostgreSQL的很多機制都是運行時候在內存,然后定期持久化到磁盤。因此clog也有一塊內存區域便于高效訪問,即clog buffers,它也屬于共享內存的這部分,平時更新clog是內存中進行的,然后滿足條件后會調用pg_fsync刷數據到磁盤上的clog文件,或者等待checkpoint刷數據。數據庫啟動時會從磁盤的pg_xact目錄下讀取事務狀態加載到clog buffers,并且運行過程中,vacuum會定時將不再使用的clog文件清理。
關于clog buffers 的大小,可以在 src/backend/access/transam/clog.c里看到相關定義。
所以clog buffers 占用的頁的個數是NBuffers / 512,最大為128個頁,最小為4個頁,這里的NBuffers 在之前wal buffer這篇文章已經說過,它和shared_buffers的關系,兩者計算的字節數是一致的,感興趣可以去看下 (PostgreSQL的wal_buffers - 墨天輪)。
因此,這里clog buffers的大小可以理解為 shared_buffers的1/512。
PostgreSQL中通過clog來存儲事務的狀態。所以,當在Postgresql中如果想要取消一個執行了很長時間的事務,基本上是瞬間完成的,而不是像Oracle中一樣需要等到undo表空間中內容回滾完,因為PostgreSQL里只需要將事務的狀態由IN_PROGRESS修改為ABORTED即可。
PG中,事務號最多占用32位,有三個是比較特殊的,在access/xlogdefs.h下可以看到,這里的BootstrapTransactionId是用于“bootstrap”操作的XID,FrozenTransactionId用于非常老的元組。FirstNormalTransactionId是第一個“正常”的事務id。
一、事務狀態
在clog.h里定義了需要提交日志clog來記錄事務的狀態,從而判斷其可見性,在PostgreSQL里總共有四種事務狀態。分別是:IN_PROGRESS、COMMITED、ABORTED和SUB_COMMITED。例如事務正在運行中,那么它的狀態就是IN_PROGRESS。全部是0是初始狀態,SUB_COMMITTED狀態表示已提交的子事務,其父事務尚未提交或中止。每個狀態只需要兩位(2 bit)就可以表示。
二、clog文件里事務id和狀態信息的空間占用
對于上述提到的四種狀態,可以用2 bit來表示。因此四個事務的狀態就占用了8 bit 即一個字節。
在src/backend/access/transam/clog.c里一樣可以找到關于這塊空間占用的定義。
CLOG_BITS_PER_XACT:每個事務占用幾個 bit(默認為2,因為4種狀態用2bit就可以完全表示)
CLOG_XACTS_PER_BYTE :每個字節可以存幾個事務的狀態(默認為4,因為1bytes=8bit,1個事務狀態需要占用2bit)
CLOG_XACTS_PER_PAGE:每個頁可以存幾個事務的狀態(8KB*4=32K=2^15)
CLOG_XACT_BITMASK:位掩碼
三、如何根據事務ID查看在clog日志里的事務的狀態
在PostgreSQL中,事務id并不是在事務開始時就會被真正分配,它會先分配一個虛擬事務號,當有數據要發生變化時才會真正分配xid,而當事務提交或回滾時,其事務狀態便會被寫入clog中。比如你顯式開啟事務,什么都不做或者只做查詢操作,commit之后,是不會消耗xid的。而當你有對數據的變更操作,則會消耗xid。
舉個例子如下,當我們執行 select txid_current();的時候,他每次也要使用一個事務號,而當我們顯式開啟事務,然后什么都不做或者只執行select操作后,commit以后,事務號是不會增加的。我測試中增加了1是因為執行了select txid_current();的原因。而當顯示事務里有對數據的變更操作,則下次執行select txid_current();的時候,事務號直接跳了兩個,減去一個select txid_current();的,剩下那個增加的事務號則是我這個insert的事務占用的。
postgres=# select txid_current();txid_current
--------------2119
(1 row)postgres=# select txid_current();txid_current
--------------2120
(1 row)postgres=# begin;
BEGIN
postgres=*# select 1;?column?
----------1
(1 row)
postgres=*# commit;
COMMIT
postgres=# select txid_current();txid_current
--------------2121
(1 row)postgres=# begin;
BEGIN
postgres=*# insert into t1 values(5);
INSERT 0 1
postgres=*# commit;
COMMIT
postgres=# select txid_current();txid_current
--------------2123
(1 row)
在src/backend/access/transam/clog.c里同同樣也存在著事務ID存放位置的定義和計算方法,如下所示
這四個分別為
TransactionIdToPage (事務id對應在哪個CLOG頁)
計算方法為:(xid) / (TransactionId) CLOG_XACTS_PER_PAGE,這個CLOG_XACTS_PER_PAGE是第二部分看到的每個頁可以存幾個事務的狀態,它默認是2^15。因此。事務id/ (2^15)得到的就是事務id對應在哪個CLOG頁,當然,是要取整的。從0號頁開始。
TransactionIdToPgIndex(事務id對應在上面頁中的偏移量)
計算方法為:(xid) % (TransactionId) CLOG_XACTS_PER_PAGE,即事務id%(2^15)得到的是在頁里的偏移量。
TransactionIdToByte(事務id對應在上面頁中第幾個的字節)
計算方法為:TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE,這里的TransactionIdToPgIndex(xid)是剛才計算的偏移量。而CLOG_XACTS_PER_BYTE是第二部分定義的每個字節可以存幾個事務的狀態,默認是4,所以事務在頁里的偏移量/4得到的是事務id對應在頁中第幾個的字節。
TransactionIdToBIndex(事務id對應在上面字節中的哪個bit)
計算方法為:(xid) % (TransactionId) CLOG_XACTS_PER_BYTE。這里 CLOG_XACTS_PER_BYTE依舊是每個字節可以存幾個事務的狀態,默認為4,此處不用偏移量。直接用事務id%4來得到在一個byte里的哪個bit。(1byte=8bit)
這里做一個驗證,
開啟一個session
另開一個session,查看clog
計算四個值,我們該條記錄是一個新的bytes里的
事務id對應在哪個CLOG頁=2108/(2^15)=0
事務id對應在上面頁中的偏移量=2108%(2^15)=2108
事務id對應在上面頁中第幾個的字節=2108/4=527
事務id對應在上面字節中的哪個bit=2108%4=0(表示這個事務在一bytes的第一組bits)
在commit后,原本的值應該變為01,但我們查看對應的clog文件部分是00,但是這可能并不代表事務在進程中,因為所有的狀態初始值都是00,clog的數據還沒有從內存寫到磁盤。而且clog分配于共享內存的clog_buffer中,當申請新的CLOG PAGE時所有的clog_buffer都沒有刷出臟頁,才需要主動選擇一個page并調用pg_fsync刷出對應的pg_clog到磁盤中,除此之外,checkpoint會將clog buffer刷到磁盤。因此我這里為了觀察選擇使用checkpoint。
此時clog buffer刷到了磁盤,可以看到此事務的狀態是01,對照開頭的狀態,是已經提交的狀態。
上邊的例子是一個TransactionIdToByte計算為整數的,當TransactionIdToByte計算帶有小數的時候,我們只看整數取整就可以了,例如如下的例子。
15從16進制轉換成2進制為 0001 0101 ,而上邊這個2110的事務,其計算的TransactionIdToBIndex(事務id對應在上面字節中的哪個bit)=2110%4=2,所以他在第3組bit上(取值是0為第一組),為01。因此在這個bytes里,我們的三個事務都是提交的狀態。
?
等到一個byte的四組事務全部都是commited的時候,hexdump -C 0000 -s 527 -n 1查看到的值應該是55,例如下面這種大量的55,如果不是55則表示這一bytes里的四組事務,不是全部提交的,存在IN PROCESS、ABORTED或者SUB_COMMITTED的事務。