Postgresql支持對元組加鎖,為了減少鎖的內存占用,直接在xmax字段記錄加鎖的事務XID。
Postgresql元組鎖有四種類型,分成不同的等級:
FOR UPDATE 對整個元組加排他鎖;
FOR NO KEY UPDATE 對非關鍵列加排他鎖;
FOR SHARE 對整個元組加共享鎖;
FOR KEY SHARE 對關鍵列加共享鎖;
元組鎖的相容性視圖如下:
Mode FOR KEY SHARE FOR SHARE FOR NO KEY UPDATE FOR UPDATE
FOR KEY SHARE Х
FOR SHARE Х Х
FOR NO KEY UPDATE Х Х Х
FOR UPDATE Х Х Х Х
其中關鍵點是FOR NO KEY UPDATE和FOR KEY SHARE是可以兼容的。
當一個元組同時加多個互相兼容的鎖時,PostgreSQL將與該元組相關聯的多個事務ID組合起來,用一個MultiXactID記錄到元組的xmax字段。
Multixact創建
舉例如下:
Session 1
postgres=# begin;
BEGIN
postgres=# select *,xmax from course for share;
cno | cname | credit | priorcourse | xmax
-----+---------+--------+-------------+------
1 | Chinese | 5 | | 633
3 | English | 4 | | 633
2 | Physics | 5 | | 633
postgres=# select txid_current();
txid_current
--------------
633
(1 row)
Session 2
postgres=# begin;
BEGIN
postgres=# select *,xmax from course for share;
cno | cname | credit | priorcourse | xmax
-----+---------+--------+-------------+------
1 | Chinese | 5 | | 633
3 | English | 4 | | 633
2 | Physics | 5 | | 633
(3 rows)
postgres=# select txid_current();
txid_current
--------------
634
(1 row)
在compute_new_xmax_infomask函數
Session1的事務已經在xmax字段記錄了自己的事務XID,因此會進入TransactionIdIsInProgress(xmax)分支,最終調用MultiXactIdCreate函數創建出一個multi_xact_id = 5。
由于加鎖的tuple總共有3個元組,為了避免創建多個multi_xact_id,每個事務會創建一個mcache,當需要創建一個新的multi_xact_id,先遍歷mcache中每個組合事務,比對組合事務中的每個成員。如果有完全相同的成員,直接復用已有的multi_xact_id=5。
Session 3
postgres=# select *,xmax from course;
cno | cname | credit | priorcourse | xmax
-----+---------+--------+-------------+------
1 | Chinese | 5 | | 5
3 | English | 4 | | 5
2 | Physics | 5 | | 5
Multixact等待
Session 4
postgres=# begin;
BEGIN
postgres=# select * from course for update;
Session4
Do_MultiXactIdWait
對multixactId成員,循環調用XactLockTableWait,直到所有的加鎖事務成員都已經提交或者回滾,才能嘗試對元組加update鎖。