1、知識準備
1)Tuple格式頭HeapTupleHeaderData
typedef struct HeapTupleFields{
TransactionId t_xmin; /* inserting xact ID */
TransactionId t_xmax; /* deleting or locking xact ID */
union{
CommandId t_cid; /* inserting or deleting command ID, or both */
TransactionId t_xvac; /* VACUUM FULL xact ID */
}t_field3;
}HeapTupleFields;
其中:
t_xmin:存儲的是產生這個元組的事務ID,可能是insert或者update語句
t_xmax:存儲的是刪除或者鎖定這個元組的事務ID
t_cid :包含cmin和cmax兩個字段,分別存儲創建這個元組的Command ID和刪除這個元組的Command ID
t_xvac:存儲的是VACUUM FULL 命令的事務ID
PostgreSQL主要就是通過t_xmin,t_xmax,cmin和cmax,ctid,t_infomask來唯一定義一個元組(t_xmin,t_xmax,cmin和cmax,ctid實際上也是一個表的隱藏的標記字段)。
2)Clog
PostgreSQL 在 CLOG 中維護事務的狀態,持久化存儲在 pg_xact 目錄下,爲了訪問高效,會在內存中維護一塊共享內存用於緩存 CLOG 的內容。
PG定義有四種事務狀態:
#define TRANSACTION_STATUS_IN_PROGRESS 0x00
#define TRANSACTION_STATUS_COMMITTED 0x01
#define TRANSACTION_STATUS_ABORTED 0x02
#define TRANSACTION_STATUS_SUB_COMMITTED 0x03
Clog文件以頁爲單位。數組下標是事務ID,參考TransactionIdGetStatus這個函數。數組內容是事務狀態,每個事務狀態佔用2bit即可。一個頁面8K,可以存儲8K*8/2=32K個事務狀態。Clog buffer大小爲Min(128, Max(4, NBuffers / 512)),初始化函數爲CLOGShmemInit。啓動時會從pg_xact讀取事務狀態加載到內存。系統運行過程中,vacuum會定時將不再使用的clog文件清理。
3)Hint
在進行可見性判斷時,需要獲取事務的狀態,即元組中 t_xmin 和 t_xmax 的狀態,這些事務狀態保存在 CLOG 中,爲加速獲取事務狀態的過程,PostgreSQL 引入了 Hint Bits。
所有 Hint Bits,就是把事務狀態直接記錄在元組頭中(HeapTupleHeaderData),避免頻繁訪問 CLOG,元組頭中對應的標識位如下:tuple->t_infomask
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
#define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */
#define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
#define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */
#define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */
#define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */
PostgreSQL 並不會在事務提交或者回滾時主動更新元組上的 Hint Bits,而是等到訪問該元組並進行可見性判斷時,如果發現 Hint Bits 沒有設置,則從 CLOG 中讀取並設置,否則直接讀取 Hint Bits 的值。判斷可見性過程中設置 Hint Bits 的函數入口爲 SetHintBits。這裏的訪問可能是 VACUUM,DML 或者 SELECT。
因此,Hint Bits 可以理解爲是事務狀態在元組頭上的一份緩存。
4)Hint與日誌
在開啓 checksum 或者 GUC 參數 wal_log_hints 爲 true 的情況下,如果 checkpoint 後第一次使頁面 dirty 的操作是更新 Hint Bits,則會產生一條 WAL 日誌,將當前頁面寫入 WAL 日誌中(即 Full Page Image),避免產生部分寫,導致數據 checksum 異常。注意,以上寫 Full Page Image 日誌的行爲與是否開啓 full_page_writes 沒有關係。因此,在開啓 checksum 或者 GUC 參數 wal_log_hints 爲 true 時,即便執行 SELECT,也可能更改頁面的 Hint Bits,從而導致產生 WAL 日誌,這會在一定程度上增加 WAL 日誌佔用的存儲空間。
2、快照判斷
判斷一個事務是否在快照,即針對這個快照是否還在in progress:
1)大於等於snap->xmax:未來事務,不可見
2)小於snap->xmin:已結束事務,可見
3)[snap->xmin,snap->xmax):查看CLOG。Commit狀態:可見;in progress和abort狀態:不可見
3、MVCC判斷可見性
可見性判斷規則可歸納爲:
/* t_xmin status = ABORTED */
Rule 1: IF t_xmin status is 'ABORTED' THEN
RETURN 'Invisible'
END IF
/* t_xmin status = IN_PROGRESS */
IF t_xmin status is 'IN_PROGRESS' THEN
IF t_xmin = current_txid THEN
Rule 2: IF t_xmax = INVALID THEN
RETURN 'Visible'
Rule 3: ELSE /* this tuple has been deleted or updated by the current transaction itself. */
RETURN 'Invisible'
END IF
Rule 4: ELSE /* t_xmin ≠ current_txid */
RETURN 'Invisible'
END IF
END IF
/* t_xmin status = COMMITTED */
IF t_xmin status is 'COMMITTED' THEN
Rule 5: IF t_xmin is active in the obtained transaction snapshot THEN
RETURN 'Invisible'
Rule 6: ELSE IF t_xmax = INVALID OR status of t_xmax is 'ABORTED' THEN
RETURN 'Visible'
ELSE IF t_xmax status is 'IN_PROGRESS' THEN
Rule 7: IF t_xmax = current_txid THEN
RETURN 'Invisible'
Rule 8: ELSE /* t_xmax ≠ current_txid */
RETURN 'Visible'
END IF
ELSE IF t_xmax status is 'COMMITTED' THEN
Rule 9: IF t_xmax is active in the obtained transaction snapshot THEN
RETURN 'Visible'
Rule 10: ELSE
RETURN 'Invisible'
END IF
END IF
END IF