天天看點

PostgreSQL系統字段cmin和cmax詳解

PG中每個表都包含了一些系統字段,其中包括cmin和cmax。

cmin:插入該元組的指令在插入事務中的指令辨別(從0開始累加)

cmax:删除該元組的指令在插入事務中的指令辨別(從0開始累加)

可以在Select指令的輸出清單中顯式地指定系統字段。

點選(此處)折疊或打開

postgres=# create table tb1(c1 int);

CREATE TABLE

postgres=# begin;

BEGIN

postgres=# insert into tb1 values(1);

INSERT 0 1

postgres=# insert into tb1 values(2);

postgres=# select xmin,xmax,cmin,cmax,* from tb1;

 xmin | xmax | cmin | cmax | c1

------+------+------+------+----

 1055 | 0 | 0 | 0 | 1

 1055 | 0 | 1 | 1 | 2

(2 行記錄)

postgres=# rollback;

ROLLBACK

cmin和cmax用于判斷同一個事務内的其他指令導緻的行版本變更是否可見。如果一個事務内的所有指令嚴格順序執行,那麼每個指令總能看到之前該事務内的所有變更,不需要使用指令辨別。然而一個事務記憶體在指令交替執行的情況,比如使用遊标進行查詢。Fetch遊标時看到的是聲明遊标時的資料快照而不是Fetch執行時,即聲明遊标後對資料的變更對該遊标不可見。

postgres=# insert into tb1(c1) values(1);

postgres=# declare tb1_v cursor for select xmin,xmax,cmin,cmax,* from tb1;

DECLARE CURSOR

postgres=# update tb1 set c1=2;

UPDATE 1

 1060 | 0 | 1 | 1 | 2

(1 行記錄)

postgres=# fetch all from tb1_v;

 1060 | 1060 | 0 | 0 | 1

行版本可見性的完整判斷邏輯課參考一下下面的代碼注釋

Source code comment in src/backend/utils/time/tqual.c:

 * ((Xmin == my-transaction &&           inserted by the current transaction

 *     Cmin my-command &&              before this command, and

 *     (Xmax is null ||                  the row has not been deleted, or

 *     (Xmax == my-transaction &&        it was deleted by the current transaction

 *     Cmax >= my-command)))             but not before this command,

 * ||                                    or

 *    (Xmin is committed &&              the row was inserted by a committed transaction, and

 *        (Xmax is null ||               the row has not been deleted, or

 *         (Xmax == my-transaction &&    the row is being deleted by this transaction

 *         Cmax >= my-command) ||        but it's not deleted "yet", or

 *         (Xmax != my-transaction &&    the row was deleted by another transaction

 *         Xmax is not committed))))     that has not been committed

出于減少系統字段大小的考慮,cmin和cmax在行版本的頭部使用同一個字段t_cid存儲,并和t_xvac交疊。是以通過系統字段看到的cmin和cmax的值總是相同的。

src/include/access/htup.h:

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; /* old-style VACUUM FULL xact ID */

    } t_field3;

} HeapTupleFields;

當xmax為0,即行版本還沒有被删除時,t_cid代表插入指令的指令辨別。

當xmax不為0,且插入事務辨別xmin和删除事務辨別xmax不同時,t_cid代表删除指令的指令辨別。

當xmax不為0,且插入事務辨別xmin和删除事務辨別xmax相同時,t_cid代表組合指令辨別。在backend的私有空間存儲了組合指令辨別到實際的{cmin,cmax}組合的映射。

執行VACUUM FULL時不需要cmin和cmax,是以t_xvac可以和t_cid共用一個存儲空間。

我們注意到不是每次執行指令,都會導緻指令辨別增加。指令辨別的配置設定規則如下:

1)每個指令使用事務内全局的指令辨別計數器的目前值作為指令辨別。

2)事務開始時,指令辨別計數器被置為初值0

3)執行更新性的SQL(包括insert,update,delete,select ... for update等)時,在SQL執行後指令辨別計數器增1

4)當指令辨別計數器經過不斷累加又回到初值0時,報錯"cannot have more than 2^32-1 commands in a transaction"

由此可得出如下結論

1)非更新性的SQL和其後的第一個更新性SQL的指令辨別相同

2)同一個事務内的插入或删除行為對目前指令有效的條件是 cmin(或cmax) 指令辨別。

這就可以解釋[3.行版本可見性的判斷]中,指令辨別比較時,有時帶=,有時不帶。