淺析Postgres中的併發控制(Concurrency Control)與事務特性(上)

PostgreSQL爲開發者提供了一組豐富的工具來管理對數據的併發訪問。在內部,數據一致性經過使用一種多版本模型(多版本併發控制,MVCC)來維護。這就意味着每一個 SQL 語句看到的都只是一小段時間以前的數據快照(一個數據庫版本),而無論底層數據的當前狀態。這樣能夠保護語句不會看到可能由其餘在相同數據行上執行更新的併發事務形成的不一致數據,爲每個數據庫會話提供事務隔離。MVCC避免了傳統的數據庫系統的鎖定方法,將鎖爭奪最小化來容許多用戶環境中的合理性能。html

使用MVCC併發控制模型而不是鎖定的主要優勢是在MVCC中,對查詢(讀)數據的鎖請求與寫數據的鎖請求不衝突,因此讀不會阻塞寫,而寫也從不阻塞讀。甚至在經過使用革新的可序列化快照隔離(SSI)級別提供最嚴格的事務隔離級別時,PostgreSQL也維持這個保證。sql

今天咱們就從基礎開始聊聊PostgreSQL內部的這一特性,聊聊MVCC和PostgreSQL中的事務特性。數據庫

這裏,咱們先用一個表格展現PostgreSQL中的事務等級,在看到後面章節時能夠翻回來看看。數組



1.基礎知識

1.1事務ID

當一個事務開始時,PostgreSQL中的事務管理系統會爲該事務分配一個惟一標識符,即事務ID(txid).PostgreSQL中的txid被定義爲一個32位的無符號整數,也就是說,它能記錄大約42億個事務。一般txid對咱們是透明的,可是咱們能夠利用PostgreSQL內部的函數來獲取當前事務的txid:併發

postgres=# BEGIN;
BEGIN
postgres=# SELECT txid_current();
 txid_current 
--------------
          233
(1 row)

在全部的txid中,有幾個值是有特殊含義的:mvc

txid = 0 表示 Invalid txid,一般做爲判斷txid的有效性使用;
txid = 1 表示 Bootstrap txid,目前狀況下,只在intidb的時候,初始化數據庫的時候使用
txid = 2 表示 Frozen txid,通常是在Vacuum時使用(在後面會提到)。函數

記住這樣一個假設:txid小的事務所修改的元組對txid大的事務來講,是可見的,反之則不可見。工具


1.2元組(tuple)結構

關於元組的結構能說的東西不少,我在這篇文章中也談到了一些。不過咱們這裏不談那麼深。關於元組(tuple)原則上能夠分紅普通的元組和TOAST元組。這裏爲簡化起見,咱們就只談論簡單的元組。post

簡單元組的結構圖以下:性能

在上圖中咱們須要瞭解的和事務相關的結構是HeapTupleHeaderData結構,這個也就是一條元組的「頭」部分。

有幾個字段須要咱們瞭解下:

  • t_xmin中保存的是插入這條元組的事務的txid

  • t_xmax中保存的是更新或者刪除這條元組的事務的txid。若是這條元組並無沒刪除或者更新,那麼t_xmax字段被設置爲0,即該字段INVALID

  • t_cid中保存的是插入這條元組的命令的id。在一個事務中可能會有多個命令,事務中的這些命令會依次被編號(從0開始遞增)。對於以下的事務: BEGIN;INSERT ;INSERT END。那麼第一個INSERT的t_cid爲0,第二個INSERT的t_cid爲1.依次類推。

  • t_ctid中保存元組的標識符(即tid)。它指向該元組自己或者該元組的新「版本」。由於PostgreSQL對記錄的修改不會直接修改tuple中的用戶數據,而是從新生成一個tuple,舊的tuple經過t_ctid指向新的tuple。若是一條記錄被修改屢次,那麼該記錄會存在多個「版本」。各版本之間經過t_ctid串聯,造成一個版本鏈。經過這個版本鏈,咱們就能夠找到最新的版本了。實際的t_ctid是一個二元組(x,y).其中x(從0開始編號)表明元組所在的page,y(從1開始編號)表示在該page的第幾個位置上。


1.3元組(tuple)的INSERT/DELETE/UPDATE操做

上面說到PostgreSQL中對記錄的修改不是直接修改tuple結構,而是從新生成一個tuple,舊的tuple經過t_ctid指向新的tuple。那麼這裏咱們依次解釋INSERT/DELETE/UPDATE是如何實現的。

咱們先上一個簡圖,這裏面的page header,line pointer這些這裏談論無關的結構我就不畫出來了。咱們重點關注tuple在page中的位置關係。

1.3.1 INSERT操做

INSERT操做最簡單,直接將一條元組放進對應的page裏面便可。以下:

這裏咱們假設

  • 1.該表在執行插入前爲空

  • 2.txid爲99的事務插入了該元組==> Tuple_1。

因而在Tuple_1中咱們能夠看到:

  • t_xmin被設置爲了99,也就是事務的txid,99.

  • t_xmax被設置爲了0,由於該元組尚未被更新或者刪除

  • t_cid被設置爲了0,很顯然,它是被事務的第一條命令插入的

  • t_ctid被設置爲了(0,1),這是這個表的第一個元組,所以它被放置在第0 page的第1個位置上。

如何從數據庫外部來驗證呢?PostgreSQL提供了一個插件:pageinspect

咱們能夠像下面那樣去查看數據庫頁面上的信息:

postgres=# CREATE EXTENSION pageinspect;
CREATE EXTENSION
postgres=# CREATE TABLE tbl (data text);
CREATE TABLE
postgres=# INSERT INTO tbl VALUES('A');
INSERT 0 1
postgres=# SELECT lp as tuple, t_xmin, t_xmax, t_field3 as t_cid, t_ctid 
                FROM heap_page_items(get_raw_page('tbl', 0));
 tuple | t_xmin | t_xmax | t_cid | t_ctid 
-------+--------+--------+-------+--------
     1 |     99 |      0 |     0 | (0,1)
(1 row)

1.3.2 DELETE操做

上面說過了,PostgreSQL中的刪除操做只是在邏輯上「刪除了」元組,實際上該元組依然留存在數據庫的存儲頁面中,只是對該元組進行了一些處理,使得其在查詢中變得「不可見」。

假設咱們在txid爲111的事務中將上面插入的元組進行刪除時,其結果以下:

咱們能夠看到,Tuple_1元組的t_xmax字段被修改了,被改爲了111,即刪除Tuple_1的事務的txid。

當txid爲111的事務被提交時,Tuple_1就成了無效的元組了,成爲「dead tuple」。

從圖上能夠看到,這個Tuple_1依然殘留在數據庫的頁面上。隨着數據庫的運行,能夠預見,這種「dead tuple」會愈來愈多。他們,會在運行VACUUM命令時被清理掉,有關VACUUM不是咱們此次討論的重點,這裏略去。

1.3.3 UPDATE操做

相對於前面兩個操做,UPDATE顯得稍微複雜一點。根據咱們上面所說,PostgreSQL對記錄的修改不會直接修改tuple中的用戶數據。PostgreSQL對UPDATE的處理是先刪除舊數據,後增長新數據。「翻譯」過來就是將要更新的舊tuple標記爲DELETE,而後再插入一條新的tuple。

一樣地,咱們假設在txid爲99的事務中插入一條記錄,而且在txid爲100的事務中被前後更新2次。過程以下:

根據咱們提到的"先刪除舊數據,後增長新數據"的原則,當執行txid爲100的事務中的第一條UPDATE命令時,操做分爲兩步:

  • 刪除舊元組Tuple_1:設置Tuple_1的t_xmax值爲當前事務的txid;

  • 新增新元組Tuple_2:設置Tuple_2的t_xmin,t_xmax,t_cid,t_ctid;設置Tuple_1的t_ctid指向Tuple_2

即:

對於Tuple_1:

t_xmax = 100;
t_ctid :  (0,1) => (0,2)

對於Tuple_2:

t_xmin = 100;
t_xmax = 0;
t_cid = 0;
t_ctid = (0,2)

同理,當執行txid爲100的事務中的第二條UPDATE命令也是如此:

對於Tuple_2:

t_xmax = 100;
t_ctid :  (0,2) => (0,3)

對於Tuple_3:

t_xmin = 100;
t_xmax = 0;
t_cid = 1;
t_ctid = (0,3)

當txid爲100的事務被提交時,Tuple_1和Tuple_2就成了「dead tuple」,反之,若是該事務被abort,則Tuple_2和Tuple_3就成了「dead tuple」。

說了以上這些,你們應該對這些操做在數據庫內部的實現有一個粗略的理解。同時,你們可能對這個ctid很好奇。這是個標記tuple所在位置的標記。那麼問題來了,tuple插入的位置是如何選定的?徹底是按照順序存放麼?若是是那樣,我以爲可能直接記錄tuple在頁面內部的偏移就行了。問題就在於這個位置的選定是有"技術"的,PostgreSQL內部有一個稱爲FSM的機制,即Free Space Map。經過它發現一個表的各個頁面的空餘空間,從而決定放在那裏比較好。具體的很少說,有興趣的能夠經過PostgreSQL的另外一個插件pg_freespacemap來探究一下。

postgres=# CREATE EXTENSION pg_freespacemap;
CREATE EXTENSION

postgres=# SELECT *, round(100 * avail/8192 ,2) as "freespace ratio"
                FROM pg_freespace('accounts');
 blkno | avail | freespace ratio 
-------+-------+-----------------
     0 |  7904 |           96.00
     1 |  7520 |           91.00
     2 |  7136 |           87.00
     3 |  7136 |           87.00
     4 |  7136 |           87.00
     5 |  7136 |           87.00
....

有了上面的這些基礎,咱們就能夠繼續討論一些較爲深刻的內容了。



2.MVCC基礎

2.1 事務提交日誌(commit log)

PostgreSQL採用的日誌主要有XLOG和CLOG,即事務日誌和事務提交日誌。XLOG是通常的日誌記錄,即一般意義上所認識的日誌記錄,它記錄了事務對數據更新的過程和事務的最終狀態。CLOG在通常的數據庫教材上是沒有說起的,其實CLOG是XLOG的一種輔助形式,記錄了事務的最終狀態。由於每一條XLOGH志記錄相對較大,若是須要經過日誌判斷一個事務的狀態,那麼使用CLOG比使用XLOG要髙效得多。同時CLOG佔用的空間也很是有限,所以它被存儲在共享內存中,能夠快速地讀取。

下面咱們就來看看clog的工做原理及其維護過程。

首先,在CLOG中,PostgreSQL定義了四個事務狀態:

IN_PROGRESS,
COMMITTED,
ABORTED,
SUB_COMMITTED.

除了最後一個狀態,其它的狀態都是「人如其名」,很少說。而SUB_COMMITTED針對的是子事務,這裏先不討論太多。

因爲存儲在共享內存中,CLOG是以8KB大小的page存儲的。在CLOG的頁面內部事務狀態是以相似"數組"的形式存儲的。其中"數組"的下標就是事務的ID,"數組"中的值即爲對應下標的事務的當前狀態。因爲CLOG只記錄事務的4個狀態,所以,只須要2個bit就能夠表示,即一個字節能夠表示4個事務狀態。在一個CLOG page(8KB)中,能夠表示8K*8b/2b=32K=2^15條CLOG記錄。

下面上例子:

在T1時刻:txid爲200的事務提交了,那麼在CLOG中txid爲200的事務的狀態從IN_PROGRESS裝換爲COMMITTED。

在T2時刻:txid爲201的事務abort了,那麼在CLOG中txid爲201的事務的狀態從IN_PROGRESS裝換爲ABORTED。

整個CLOG就這樣一直追溯着事務系統中全部事務的狀態。若是有新的事務則直接在"數組"後面添加。噹噹前頁面(8KB大小)已經存滿時,則增長一個新的page進行記錄。

當PostgreSQL數據庫shutdown或者當進行checkpoint時,CLOG中的數據會被寫回到pg_clog目錄下。打開pg_clog目錄,咱們會發現一些命名爲0000,0001之類的文件。這些即爲CLOG文件。這些文件的最大的size爲256KB。也就是說,一個文件最多能夠存儲256KB/8kB=32個CLOG。若是當前有8個CLOG,那麼0000文件就能夠存下來。而若是當前有35個CLOG,那麼就須要0000,0001兩個文件來存儲,以此類推。

而當PostgreSQL數據庫啓動的時候,pg_clog文件夾下的CLOG記錄又會被讀到共享內存中。


2.2 事務快照(Transaction Snapshot)

終於要說到事務快照了。

事務快照我認爲是一個很形象的詞,很容易從字面上理解它的意味。所謂"快照",就是拿着照相機"咔嚓"一聲記錄下當前瞬間的信息,在按下快門後所發生的變化咱們通通沒法察覺,即invisible。

一樣地,事務快照就是當某一個事務執行期間,記錄的某一個時間點上哪些事務是active,所謂active,就是說這個事務要麼在執行中(in progress),要麼尚未開始。

PostgreSQL有內置的函數能夠獲取當前的數據快照:txid_current_snapshot()函數
經過該函數能夠獲取當前的快照,可是快照信息解讀起來須要費一點腦筋。

postgres=# SELECT txid_current_snapshot();
 txid_current_snapshot 
-----------------------
 100:104:100,102
(1 row)

上面的數據快照是這樣一個數字序列構成: xmin:xmax:xip_list

下面我來一一介紹各個字段的意義。

  • xmin

最先的active的事務的txid,即txid值最小的active事務;全部txid小於該值的事務,若是1.狀態爲commited則爲visible,2.狀態aborted則爲dead,

  • xmax

第一個還未分配的txid,全部txid大於該值的事務在快照生成時還沒有開始,即不可見

  • xip_list

快照生成時全部active的事務的txid。

對照上面的解釋,咱們來兩個有表明性的例子吧:

  • 對於(a):

txid小於100的事務都不是active的;

txid大於等於100的事務都是active的。

  • 對於(b)

txid小於100的事務都不是active的;

txid大於等於104的事務都是active的;

txid爲100和102都是active的,101和103都不是active的。

那麼咱們疑惑了,咱們花這麼多篇幅去判斷一個事務是否是active有什麼意義呢?

下面來了。

在PostgreSQL中,當一個事務的隔離級別是READ COMMITTED時,在該事務中的每一個命令執行以前都會從新獲取一次snapshot,而若是一個事務的隔離級別是REPEATABLE READ或者SERIALIZABLE時,該事務只在第一條命令執行時獲取snapshot。

記住以上的差異,正是以上的差異引發tuple的可見性的差異,從而實現不一樣的隔離級別。

仍是來一張圖來解釋上面的話吧。

咱們假設上面的三個事務依次執行,其中Transaction_A 和Transaction_B的隔離級別都是READ COMMITTED,Transaction_C的隔離級別是REPEATABLE READ。咱們分時間段T1~T5來解釋:

T1

Transaction_A開始並執行第一條命令,此時獲取txid和snapshot。事務系統給Transaction_A分配txid爲200,並獲取當前快照爲 200:200:

T2

Transaction_B開始並執行第一條命令,此時獲取txid和snapshot。事務系統給Transaction_B分配txid爲201,並獲取當前快照爲 200:200:,由於Transaction_A正在執行中,因此Transaction_B沒法看到Transaction_A中的修改。

T3

Transaction_C開始並執行第一條命令,此時獲取txid和snapshot。事務系統給Transaction_C分配txid爲202,並獲取當前快照爲 200:200:,由於Transaction_A正在執行中,因此Transaction_C沒法看到Transaction_A和Transaction_B中的修改。

T4

Transaction_A進行了commit。事務管理系統刪除了Transaction_A的信息。

T5

Transaction_B和Transaction_C分別執行它們的SELECT命令。

此時,Transaction_B獲取一個新的snapshot(由於它的隔離級別是READ COMMITTED),該snapshot爲 201:201:。由於Transaction_A的已經提交。Transaction_A對Transaction_B可見。

同時,因爲Transaction_C的隔離級別是REPEATABLE READ,它仍然使用第一條命令執行時得到的snapshot 200:200 ,所以Transaction_A和Transaction_B仍然對Transaction_C不可見。


2.3元組可見性規則

所謂"元組可見性(Tuple Visibility)"的規則就是利用:

1.tuple中的t_xmin和t_xmax字段;
2.clog
3.當前的snapshot

來判斷一個tuple對當前事務中的執行語句是可見仍是不可見。所謂可見與不可見就是說當前命令在處理時是否要處理該tuple。
仍是爲了簡便起見,咱們迴避了子事務和有關t_ctid的問題。只討論最簡單的情形。

咱們選取十條規則並將他們分爲三類進行說明。

1. t_xmin的狀態爲ABORTED

咱們知道t_xmin是一個tuple被INSERT時的事務txid。若是該事務的狀態爲ABORTED,說明該事務被取消,那麼理所固然該事務所INSERT的tuple天然是無效而且是不可見的。因此Rule1即爲:

Rule 1: If Status(Tuple.t_xmin) = ABORTED ⇒ Tuple is Invisible

2. t_xmin的狀態爲IN_PROGRESS

若是一個tuple的t_xmin的狀態爲IN_PROGRESS,那麼很大可能它是不可見的。

由於:

若是這個tuple是其它事務(非當前事務)所插入的,那麼這個tuple顯然是不可見的,由於這個tuple還未提交(postgreSQL不支持讀未提交)。

Rule 2: If Status(t_xmin) = IN_PROGRESS && t_xmin ≠ current_txid ⇒ Tuple is Invisible

若是這個tuple是當前事務提交的,並tuple的t_xmax值不是0,即該tuple是由當前事務插入,可是被當前事務UPDATE或者DELETE過了,所以,顯然是不可見的。

Rule 3: If Status(t_xmin) = IN_PROGRESS && t_xmin = current_txid && t_xmax ≠ INVAILD ⇒ Tuple is Invisible

反之,若是這個tuple是當前事務提交的,並tuple的t_xmax值是0,說明這個tuple是由當前事務插入而且並無被修改過,因此,它是可見的。

Rule 4: If Status(t_xmin) = IN_PROGRESS && t_xmin = current_txid && t_xmax = INVAILD ⇒ Tuple is Visible

3. t_xmin的狀態爲COMMITTED

和上面的相反,若是一個tuple的t_xmin的狀態爲COMMITTED,那麼很大可能它是可見的。

先把規則列出來,後面再解釋。

Rule 5: If Status(t_xmin) = COMMITTED && Snapshot(t_xmin) = active ⇒ Tuple is Invisible

Rule 6: If Status(t_xmin) = COMMITTED && Snapshot(t_xmin) ≠ active && (t_xmax = INVALID || Status(t_xmax) = ABORTED) ⇒ Tuple is Visible

Rule 7: If Status(t_xmin) = COMMITTED && Status(t_xmax) = IN_PROGRESS && t_xmax = current_txid ⇒ Tuple is Invisible

Rule 8: If Status(t_xmin) = COMMITTED && Status(t_xmax) = IN_PROGRESS && t_xmax ≠ current_txid ⇒ Tuple is Visible

Rule 9: If Status(t_xmin) = COMMITTED && Status(t_xmax) = COMMITTED && Snapshot(t_xmax) = active ⇒ Tuple is Visible

Rule 10: If Status(t_xmin) = COMMITTED && Status(t_xmax) = COMMITTED && Snapshot(t_xmax) ≠ active ⇒ Tuple is Invisible

Rule5是比較顯然的,對於一個tuple,插入它的事務已經提交(COMMITED),而且該事務在當前的snapshot下是active的,說明該事務對當前事務中的命令來講是 in progress 或者 not yet started(忘記的話,看下2.2節的內容),故該事務插入的tuple對在當前爲不可見;

Rule6,顯然,該tuple沒有被修改或者修改它的事務被abort了 = > 該tuple沒有被修改;插入該tuple的事務x在當前snapshot中是inactive(inactive說明事務x對於當前要執行的SQL命令來講要麼被提交了,要麼被abort了),因此可見;

Rule7,若是tuple被當前事務UPDATE或者DELETE了,天然這個tuple對於咱們來講是舊版本了,不可見;

Rule8,和Rule7對比,這個tuple是被別的事務x修改(UPDATE或者DELETE)了,並且事務x沒有被提交(postgreSQL不支持讀未提交),因此修改後的元組對咱們不可見,咱們能看到的仍是當前這個元組,因此當前tuple可見;

Rule9,雖然修改這個tuple的事務x已經提交了,可是事務x在當前snapshot中是active的,即對當前事務中的命令來講是 in progress 或者 not yet started(第二次用到這個假設了),因此事務x的修改對當前命令不可見,因此咱們看到了仍是這個tuple;

Rule10,和上一條相反,修改這個tuple的事務x已經提交了,而且事務x在當前snapshot中是inactive(inactive說明事務x對於當前要執行的SQL命令來講要麼被提交了,要麼被abort了)的,因此對當前事務中的命令來講,這個事務x已經提交,因此這個tuple對當前事務中的命令而言,已是被修改過了,便是舊版本的了,因此即爲不可見。



3.PostgreSQL中的MVCC

又交代了一些基礎知識,下面正式進入MVCC。

3.1 元組可見性檢測

這一節咱們來談論PostgreSQL中的"元組可見性"的檢測。

所謂"元組可見性"的檢測就是利用元組可見性規則來判斷一個tuple對當前事務中的執行語句是可見仍是不可見。咱們知道在PostgreSQL中是tuple是多版本,那麼對於一個事務中的命令來講,它須要找到對應事務中當前命令應該看到的那個版本的tuple進行處理。

經過"元組可見性"的檢測不只僅能夠幫助找到正確"版本"的tuple,同時還能夠用來解決 ANSI SQL-92 標準中定義的異常:

髒讀;
不可重複讀;
幻讀

即,能夠實現不一樣的事務隔離級別。

仍是上例子吧:

簡化起見,txid=200的事務的隔離級別爲READ COMMITTED,txid=201的事務的隔離級別咱們分READ COMMITTED或者REPEATABLE READ兩種狀況討論。

上圖中命令的執行順序以下:

T1 :txid=200的事務開始

T2 :txid=201的事務開始

T3 :txid=200和txid=201的事務分別執行SELECT命令

T4 :txid=200的事務執行UPDATE命令

T5 :txid=200和txid=201的事務分別執行SELECT命令

T6 :txid=200的事務commit

T7 :txid=201的事務執行SELECT命令

下面咱們就來看看PostgreSQL是如何執行"元組可見性"檢測的。

T3
在T3時刻,當前表中只有Tuple_1,根據Rule6該tuple對全部事務可見;

T5
在T5時刻的狀況有所不一樣,咱們對兩個事務分開討論。

對於txid = 200的事務,此刻,咱們可知Tuple_1是不可見的(根據Rule7),Tuple_2可見(根據Rule4);
所以,此時SELECT語句的返回結果爲:

postgres=# -- txid 200
postgres=# SELECT * FROM tbl;
 name 
------
 Hyde
(1 row)

對於txid = 201的事務,此刻,咱們可知Tuple_1是不可見的(根據Rule8),Tuple_2一樣不可見(根據Rule2);
所以,此時SELECT語句的返回結果爲:

postgres=# -- txid 201
postgres=# SELECT * FROM tbl;
  name 
--------
 Jekyll
(1 row)

咱們能夠看到,這裏txid = 201的事務不會讀取txid = 200的事務的未提交的更新,也就是迴避了髒讀問題。在PostgreSQL全部的事務隔離級別中都不會形成髒讀

T7
在T7時刻,只有txid = 201的事務還在運行,txid = 200的事務已經提交。如今咱們分兩種狀況來討論txid = 201的事務的行爲。

  • 1)txid = 201的事務的隔離級別爲READ COMMITTED

    此時因爲txid = 200的事務已經提交,所以,此時從新獲取的snapshot爲 201:201: 。所以,咱們能夠知道Tuple_1是不可見的(根據Rule10),Tuple_2是可見的(根據Rule6),

所以,在READ COMMITTED級別下,SELECT語句的返回結果爲:

postgres=# -- txid 201 (READ COMMITTED)
postgres=# SELECT * FROM tbl;
 name 
------
 Hyde
(1 row)

咱們能夠看到,該事務在隔離級別爲READ COMMITTED時,先後兩次相同的SELECT獲取的結果不一樣,也就是不可重複讀

  • 2)txid = 201的事務的隔離級別爲REPEATABLE READ

    此時雖然txid = 200的事務已經提交,可是咱們知道在REPEATABLE READ/SERIALIZABLE時,事務只在執行第一條命令時獲取一次snapshot,所以,此時snapshot仍保持不變爲 200:200: 。所以,咱們能夠知道Tuple_1是不可見的(根據Rule9),Tuple_2是可見的(根據Rule5),

所以,在READ COMMITTED級別下,SELECT語句的返回結果爲:

postgres=# -- txid 201 (READ COMMITTED)
postgres=# SELECT * FROM tbl;
  name 
--------
 Jekyll
(1 row)

咱們能夠看到,事務在隔離級別爲REPEATABLE READ時,先後兩次相同的SELECT獲取的結果不變,即迴避了不可重複讀。

到這裏,咱們已經解決了解決了髒讀不可重複讀的問題,那麼還有一個幻讀。然而幻讀在PostgreSQL的事務在隔離級別爲REPEATABLE READ時存在麼?

咱們知道,幻讀的定義是:一個事務從新執行一個返回符合一個搜索條件的行集合的查詢, 發現知足條件的行集合由於另外一個最近提交的事務而發生了改變。

顯然,在PostgreSQL中,因爲快照隔離機制,咱們繼續上面的分析就能發現:在REPEATABLE READ/SERIALIZABLE隔離級別時消除了幻讀(篇幅限制,我就不寫了),即在從REPEATABLE READ開始就回避了幻讀的問題,這和其它數據庫上不太同樣,PostgreSQL中提供的隔離級別更加嚴格。


關於這個系列,還剩下"如何避免Lost Update"和"SSI(Serializable Snapshot Isolation)"這兩個問題沒討論,篇幅太長了,並非寫論文,容樓主喝口茶,留在下篇再說吧~

相關文章
相關標籤/搜索