論Postgres的“已提交的而且xmin’比當前事務的XID小的記錄對當前事務才是可見的”

lottu發表於2016-09-18

1.闡述

最近在網上看到這樣一句話Postgres“已提交的而且 xmin 比當前事務的XID小的記錄對當前事務才是可見的”。先不評斷這句話的正確性;看下這句話的結構,因果關係;

按照此話的意思;要postgres中的資料可見必須滿足兩個必要條件:
  1. 事務已經提交(commit);
  2. 提交時插入記錄的xmin 小於 當前current_txid(事務id)。
而網上對這句話的解釋:“這意味著,你可以開始一個新事務然後插入一行記錄,直到你提交(COMMIT)之前,你插入的這行記錄對其他事務永遠都是不可見的。等到提交以後,其他後建立的新事務就可以看到這行新記錄了,因為他們滿足了 xmin < XID 條件,而且建立哪一行記錄的事務也已經完成”。看起來挺合理的,無懈可擊似的。接下來我們來推敲推敲。要說記錄的可見性;這還得從事務的隔離級別說起。

2. 舉例驗證

根據《PostgreSQL9.4.4-CN-v1.0.pdf》文件介紹: SQL標準定義了四個級別的事務隔離 { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }; postgres目前只實現了 {SERIALIZABLE | REPEATABLE READ | READ COMMITTED }這三種。詳細大家去看文件;這裡不做介紹。
  • 事務隔離級別READ COMMITTED
事務隔離級別:讀已提交(READ COMMITTED)這是postgres,greenplum預設的事務隔離級別。若從先事務隔離級別(讀已提交)來解釋:就是讀已經提交的記錄;是不是這樣呢? 來驗證下。
--session A  事務id為1844;
postgres=# begin;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1844
         
--session B 事務id為1845;並在插入一條記錄在lottu05表(未提交)
postgres=# begin;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1845

postgres=# insert into lottu05 values (1001,`lottu`);
INSERT 0 1

--在session A/B檢視記錄; session A讀不到記錄; session B可以讀到記錄。
postgres=# select * from lottu05;
 id | name 
----+------
(0 rows)

--在session B提交插入的記錄;在檢視session A是否可以看到記錄。
postgres=# select xmin,* from lottu05;
 xmin |  id  | name  
------+------+-------
 1845 | 1001 | lottu
--表明session A(當前事務為ID:1844)可以讀 插入記錄事務id為1845 已經提交的記錄。
--總結: 事務隔離級別為讀已提交(READ COMMITTED)就是讀已經提交的記錄。
由此可見,對讀已提交隔離級別而言"已提交的而且 xmin’比當前事務的XID小的記錄對當前事務才是可見的"是不正確的。
而網上的解釋:也是必要不充分條件。那該如何詮釋這說話呢?請看下文講解
是根據當前postgres系統的當前事務ID相比;目前系統下一個事務ID為1846
-- 我們現在看下當前postgres系統 下一個事務id
[postgres@localhost ~]$ pg_controldata |grep  NextXID
Latest checkpoint`s NextXID:          0/1846
--意思是說這條記錄後面開啟會話從事務id:1846是可見的。不充分的是事務ID:1844也可以讀到該記錄。
--然而這句話來源何處;我想是有依據的。接下來我們做一個實驗。模擬postgrs穿越到過去。 
                        
--session C 現在插入1002-1008條記錄;結果如下:
postgres=# select xmin,id,name from lottu05;
 xmin |  id  |  name   
------+------+---------
 1845 | 1001 | lottu
 1846 | 1002 | lottu02
 1847 | 1003 | lottu03
 1848 | 1004 | lottu04
 1849 | 1005 | lottu05
 1850 | 1006 | lottu06
 1851 | 1007 | lottu07
 1852 | 1008 | lottu08   

--我們現在使用將資料庫postgres回到 txid 為1849。注意:該動作不建議操作;
[postgres@localhost ~]$ pg_stop
waiting for server to shut down.......... done
server stopped
[postgres@localhost ~]$ pg_resetxlog -x 1849 $PGDATA
Transaction log reset
[postgres@localhost ~]$ pg_start
server starting
[postgres@localhost ~]$ psql
psql (9.5.0)
Type "help" for help.

postgres=# select xmin,id,name from lottu05;
 xmin |  id  |  name   
------+------+---------
 1845 | 1001 | lottu
 1846 | 1002 | lottu02
 1847 | 1003 | lottu03
 1848 | 1004 | lottu04
 1849 | 1005 | lottu05
--可以看到上面的xmin:(1850-1852)是不可見的。
--等資料庫的事務ID超過1852;這些資料可以展示出來。
postgres=# select txid_current();
 txid_current 
--------------
         1850

postgres=# select txid_current();
 txid_current 
--------------
         1851

postgres=# select txid_current();
 txid_current 
--------------
         1852

postgres=# select xmin,id,name from lottu05;
 xmin |  id  |  name   
------+------+---------
 1845 | 1001 | lottu
 1846 | 1002 | lottu02
 1847 | 1003 | lottu03
 1848 | 1004 | lottu04
 1849 | 1005 | lottu05
 1850 | 1006 | lottu06
 1851 | 1007 | lottu07
 1852 | 1008 | lottu08 
 
--從這個實驗看來 確實是需要滿足網上所說的兩個條件。上面也提過;該操作不建議操作。設想;當前時代若可以穿越到歷史上各個時代;那歷史不亂套了嗎?同理如此。
所以說對隔離級別為READ COMMITTED而言;如同它字面解釋一樣;只要記錄COMMITTED;就可以讀到。
 注意:
--1.該操作不等同 oracle的flashback操作;雖然回到了歷史;歷史上已經發生的還是會發生。
--2.該操作並不能做資料恢復操作。若對資料做刪除進行恢復;可以參考--http://www.cnblogs.com/lottu/p/5761885.html  

總結:對隔離級別為READ COMMITTED而言;如同它字面解釋一樣;只要記錄COMMITTED;就可以讀到
  • 事務隔離級別:REPEATABLE READ
事務隔離級別:REPEATABLE READ;是不是如同它而言呢?接下來拭目以待吧。
--開啟SESSION A; ctid為1857。
postgres=# truncate table lottu05;
TRUNCATE TABLE
postgres=# begin;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1857

--開啟session B;隔離級別為REPEATABLE READ。事務id為:1858
postgres=# begin ISOLATION LEVEL REPEATABLE READ;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1858  

--在session A插入 10條記錄並提交
postgres=# insert into lottu05 select generate_series(1001,1010),`lottu`||generate_series(1,10);
INSERT 0 10
postgres=# commit;
COMMIT

--在session B檢視是否可以讀到記錄
postgres=# select * from lottu05;
 id | name 
----+------
(0 rows)       

--結果表明session B 讀不到 已經提交且 事務ID:1857比session B的事務ID為1858要小的記錄。

3.總結

對Postgres記錄的可見性;對網上這句話“已提交的而且 xmin 比當前事務的XID小的記錄對當前事務才是可見的”可以修正為“已提交的而且 xmin 比當前系統事務的XID小或者等於的記錄對當前事務才是可見的”。而對它的解釋(這意味著,你可以開始一個新事務然後插入一行記錄,直到你提交(COMMIT)之前,你插入的這行記錄對其他事務永遠都是不可見的。等到提交以後,其他後建立的新事務就可以看到這行新記錄了,因為他們滿足了 xmin < XID 條件,而且建立哪一行記錄的事務也已經完成”)是充分不必要;
–參考文獻


相關文章