SQL Server中有個rowversion,利用它可以實作樂觀鎖政策的并發更新。那麼在PostgreSQL中有沒有類似的東西呢?
PostgreSQL中,最接近rowversion的就是系統隐藏列xmin。而且在hibernate的PostgreSQL方言中,也是使用xmin作為行版本的辨別使用。
xmin是插入該行版本的事務辨別(事務ID)。PostgreSQL每次更新行都會建立一個新的行版本,是以如果其他事務修改了之前看到的記錄,那麼這條記錄的xmin必然會變更。
點選(此處)折疊或打開
postgres=# create table tb1(id int,name text);
CREATE TABLE
postgres=# insert into tb1 values(1,'a');
INSERT 0 1
postgres=# update tb1 set name='b' where id=1;
UPDATE 1
postgres=# select xmin,* from tb1;
xmin | id | name
-------+----+------
55815 | 1 | b
(1 row)
但是使用xmin作為行版本辨別不能差別同一個事務内的兩次修改。
postgres=# begin;
BEGIN
postgres=# update tb1 set name='c' where id=1;
55819 | 1 | c
postgres=# end;
COMMIT
不過,其實我們不需要為這個小小的瑕疵擔心。
因為,事務内的第一次修改對其他事務不可見,唯一能看見它的隻有修改這一行的事務自己。如果認為事務自己的第二次修改和自己的第一次修改沖突是不是有點荒謬。是以我們可以認為事務的第二次修改覆寫第一次修改是應用自己願意。
好了,還真有鑽牛角尖的。以上的差異畢竟導緻了和SQL Server的rowversion的行為上的微不足道的差異,但為了避免在解釋這種差異上多費口舌,我們試圖找一種和SQL Server完全一緻的方案。
首先我們想到了xmin+cmin。cmin代表了插入事務内部的指令辨別。xmin+cmin的組合不就完美了嗎。
先不考慮,組合兩個字段作為行版本辨別在使用上的不便,這個方法還有很大的漏洞。
因為在PostgreSQL内部,cmin和cmax使用的是共用的同一個存儲域,就好像C語言中的聯合。是以更新和删除操作也會修改cmin。
下面這個例子中一個復原的更新修改了cmin,如果把cmin作為行版本号一部分使用,就會誤判斷為發生更新沖突了。
postgres=# select xmin,cmin,cmax,* from tb1;
xmin | cmin | cmax | id | name
-------+------+------+----+------
55822 | 0 | 0 | 1 | c
postgres=# insert into tb1 values(2,'a');
postgres=# update tb1 set name='d' where id=1;
postgres=# rollback;
ROLLBACK
55822 | 1 | 1 | 1 | c
除此以外,還有一個候選是ctid。但是每次VACUUM FULL之後, 一個行的ctid都會被更新或者移動。是以如果能夠容忍VACUUM FULL帶來的更新沖突的誤判斷,也可以考慮。
轉了一圈,最後我還是認為xmin是作為行版本号的最佳方案。
參考:
關于PostgreSQL中的幾個系統隐藏列,可參考手冊
http://58.58.27.50:8079/doc/html/9.3.1_zh/ddl-system-columns.html
------------------------------------------------------------------------
每個表都有幾個系統字段,這些字段是由系統隐含定義的。是以, 這些名字不能用于使用者定義的字段名。請注意這些限制與這個名字是否關鍵字無關, 把名字用引号括起來并不能讓你逃離這些限制。你實際上不需要注意這些字段; 隻要知道它們存在就可以了。
oid
tableoid
xmin
插入該行版本的事務辨別(事務ID)。注意:在這個環境裡,一個行版本是一行的一個狀态; 一行的每次更新都為同一個邏輯行建立一個新的行版本。
cmin
在插入事務内部的指令辨別(從零開始)。
xmax
删除事務的辨別(事務ID),如果不是被删除的行版本,那麼是零。在一個可見行版本裡, 這個字段有可能是非零。這通常意味着删除事務還沒有送出,或者是一個删除的企圖被復原掉了。
cmax
删除事務内部的指令辨別符,或者是零。
ctid
一個行版本在它所處的表内的實體位置。請注意,盡管ctid 可以用于非常快速地定位行版本,但每次VACUUM FULL之後, 一個行的ctid都會被更新或者移動。是以ctid 是不能作為長期的行辨別符的。應該使用 OID ,或者更好是使用者定義的序列号,來辨別一個邏輯行。