全部產品
Search
文件中心

PolarDB:事務隔離

更新時間:Jul 06, 2024

SQL標準定義了四種隔離等級。最嚴格的是可序列化,在標準中用了一整段來定義它,其中說到一組可序列化事務的任意並發執行被保證效果和以某種順序一個一個執行這些事務一樣。

其他三種層級使用並發事務之間互動產生的現象來定義,每一個層級中都要求必須不出現一種現象。注意由於可序列化的定義,在該層級上這些現象都不可能發生(這並不令人驚訝--如果事務的效果與每個時刻只運行一個的相同,你怎麼可能看見由於互動產生的現象?)。在各個層級上被禁止出現的現象是:

  • 髒讀 一個事務讀取了另一個並行未提交事務寫入的資料。

  • 不可重複讀取 一個事務重新讀取之前讀取過的資料,發現該資料已經被另一個事務(在初始讀之後提交)修改。

  • 幻讀 一個事務重新執行一個返回符合一個搜尋條件的行集合的查詢, 發現滿足條件的行集合因為另一個最近提交的事務而發生了改變。

  • 序列化異常 成功提交一組事務的結果與這些事務所有可能的串列執行結果都不一致。

SQL 標準和 PostgreSQL 實現的交易隔離等級在表 1 中描述。

表 1. 交易隔離等級

隔離等級

髒讀

不可重複讀取

幻讀

序列化異常

讀未提交

允許,但不在 PG 中

可能

可能

可能

讀已提交

不可能

可能

可能

可能

可重複讀

不可能

不可能

允許,但不在 PG 中

可能

可序列化

不可能

不可能

不可能

不可能

在本資料庫中,你可以請求四種標準交易隔離等級中的任意一種,但是內部只實現了三種不同的隔離等級,即 PostgreSQL 的讀未提交模式的行為和讀已提交相同。這是因為把標準隔離等級映射到 PostgreSQL 的多版本並發控制架構的唯一合理的方法。該表格也顯示 PostgreSQL 的可重複讀實現不允許幻讀。而 SQL 標準允許更嚴格的行為:四種隔離等級只定義了哪種現像不能發生,但是沒有定義哪種現像必須發生。可用的隔離等級的行為在下面的小節中詳細描述。要設定一個事務的交易隔離等級,使用SET TRANSACTION命令。

重要

某些本資料庫資料類型和函數關於事務的行為有特殊的規則。特別是,對一個序列的修改(以及用serial聲明的一列的計數器)是立刻對所有其他事務可見的,並且在作出該修改的事務中斷時也不會被復原。

讀已提交隔離等級

讀已提交是本資料庫中的預設隔離等級。當一個事務使用這個隔離等級運行時, 一個查詢(沒有FOR UPDATE/SHARE子句)只能看到查詢開始之前已經被提交的資料,而無法看到未提交的資料或在查詢執行期間其它事務提交的資料。實際上,SELECT查詢看到的是一個在查詢開始啟動並執行瞬間該資料庫的一個快照。不過SELECT可以看見在它自身事務中之前執行的更新的效果,即使它們還沒有被提交。還要注意的是,即使在同一個事務裡兩個相鄰的SELECT命令可能看到不同的資料,因為其它事務可能會在第一個SELECT開始和第二個SELECT開始之間提交。

UPDATEDELETESELECT FOR UPDATESELECT FOR SHARE命令在搜尋目標行時的行為和SELECT一樣:它們將只找到在命令開始時已經被提交的行。不過,在被找到時,這樣的目標行可能已經被其它並發事務更新(或刪除或鎖住)。在這種情況下,即將進行的更新將等待第一個更新事務提交或者復原(如果它還在進行中)。如果第一個更新交易回復,那麼它的作用將被忽略,並且第二個事務可以繼續更新最初發現的行。如果第一個更新事務提交,若該行被第一個更新者刪除,則第二個更新事務將忽略該行,否則第二個更新者將試圖在該行的已被更新的版本上應用它的操作。該命令的搜尋條件(WHERE子句)將被重新計算,以確定該行被更新的版本是否仍然符合搜尋條件。如果符合,則第二個更新者使用該行的已更新版本繼續其操作。在SELECT FOR UPDATESELECT FOR SHARE的情況下,這意味著把該行的已更新版本鎖住並返回給用戶端。帶有ON CONFLICT DO UPDATE子句的INSERT行為類似。在讀已提交模式下,要插入的每一行將被插入或者更新。除非有不相關的錯誤出現,這兩種結果之一是肯定會出現的。如果在另一個事務中發生衝突,並且其效果對於INSERT還不可見,則UPDATE子句將會影響那個行,即便那一行對於該命令來說沒有慣常的可見版本。帶有ON CONFLICT DO NOTHING子句的INSERT有可能因為另一個效果對INSERT快照不可見的事務的結果無法讓插入進行下去。再一次,這隻是讀已提交模式中的情況。因為上面的規則,正在更新的命令可能會看到一個不一致的快照:它們可以看到並發更新命令在它嘗試更新的相同行上的作用,但是卻看不到那些命令對資料庫裡其它行的作用。這樣的行為令讀已提交模式不適合用於涉及複雜搜尋條件的命令。不過,它對於更簡單的情況是正確的。例如,考慮用這樣的命令更新銀行餘額:

    BEGIN;
    UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345;
    UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534;
    COMMIT;

如果兩個這樣的事務同時嘗試修改帳號 12345 的餘額,那我們很明顯希望第二個事務從賬戶行的已更新版本上開始工作。 因為每個命令隻影響一個已經決定了的行,讓它看到行的已更新版本不會導致任何麻煩的不一致性。在讀已提交模式中,更複雜的使用可能產生不符合需要的結果。例如: 考慮一個在資料上操作的DELETE命令,它操作的資料正被另一個命令從它的限制條件中移除或者加入,例如,假定website是一個兩行的表,兩行的website.hits等於910

    BEGIN;
    UPDATE website SET hits = hits + 1;
    -- run from another session:  DELETE FROM website WHERE hits = 10;
    COMMIT;

即便在UPDATE之前有一個website.hits = 10的行,DELETE將不會產生效果。這是因為更新之前的行值9被跳過,並且當UPDATE完成並且DELETE獲得一個鎖,新行值不再是10而是11,這再也不匹配條件了。因為在讀已提交模式中,每個命令都是從一個新的快照開始的,而這個快照包含在該時刻已提交的事務, 因此同一事務中的後續命令將看到任何已提交的並行事務的效果。以上的焦點在於單個命令是否看到資料庫的絕對一致的視圖。讀已提交模式提供的部分事務隔離對於許多應用而言是足夠的,並且這個模式速度快並且使用簡單。 不過,它不是對於所有情況都夠用。做複雜查詢和更新的應用可能需要比讀已提交模式提供的更嚴格一致的資料庫檢視。

可重複讀隔離等級

可重複讀隔離等級只看到在事務開始之前被提交的資料;它從來看不到未提交的資料或者並行事務在本事務執行期間提交的修改(不過,查詢能夠看見在它的事務中之前執行的更新,即使它們還沒有被提交)。這是比 SQL 標準對此隔離等級所要求的更強的保證,並且阻止表 1 中描述的除了序列化異常之外的所有現象。如上面所提到的,這是標準特別允許的,標準只描述了每種隔離等級必須提供的最小保護。這個層級與讀已提交不同之處在於,一個可重複讀事務中的查詢可以看見在事務中第一個非事務控制語句開始時的一個快照,而不是事務中當前語句開始時的快照。因此,在一個單一事務中的後續SELECT命令看到的是相同的資料,即它們看不到其他事務在本事務啟動後提交的修改。使用這個層級的應用必須準備好由於序列化失敗而重試事務。UPDATEDELETESELECT FOR UPDATESELECT FOR SHARE命令在搜尋目標行時的行為和SELECT一樣: 它們將只找到在事務開始時已經被提交的行。 不過,在被找到時,這樣的目標行可能已經被其它並發事務更新(或刪除或鎖住)。在這種情況下, 可重複讀事務將等待第一個更新事務提交或者復原(如果它還在進行中)。 如果第一個更新交易回復,那麼它的作用將被忽略並且可重複讀事務可以繼續更新最初發現的行。 但是如果第一個更新事務提交(並且實際更新或刪除該行,而不是只鎖住它),則可重複讀事務將復原並帶有如下訊息

    ERROR:  could not serialize access due to concurrent update

因為一個可重複讀事務無法修改或者鎖住被其他在可重複讀事務開始之後的事務改變的行。當一個應用接收到這個錯誤訊息,它應該中斷當前事務並且從開頭重試整個事務。在第二次執行中,該事務將見到作為其初始資料庫檢視一部分的之前提交的改變,這樣在使用行的新版本作為新事務更新的起點時就不會有邏輯衝突。注意只有更新事務可能需要被重試;唯讀事務將永遠不會有序列化衝突。可重複讀模式提供了一種嚴格的保證,在其中每一個事務看到資料庫的一個完全穩定的視圖。不過,這個視圖並不需要總是和同一層級上並發事務的某些序列化(一次一個)執行保持一致。例如,即使這個層級上的一個唯讀事務可能看到一個控制記錄被更新,這顯示一個批處理已經被完成但是不能看見作為該批處理的邏輯組成部分的一個細節記錄,因為它讀取空值記錄的一個較早的版本。如果不小心地使用顯式鎖來阻塞衝突事務,嘗試用運行在這個隔離等級的事務來強制商務規則不太可能正確地工作。可重複讀隔離等級是使用學術資料庫文獻和一些其他資料庫產品中稱為Snapshot Isolation的已知的技術來實現的。 與使用傳統鎖技術並降低並發性的系統相比,可以觀察到行為和效能方面的差異。 一些其他系統甚至可以提供可重複讀取和快照隔離作為具有不同行為的不同隔離等級。 直到 SQL 標準開發出來之後,資料庫研究人員才正式確定區分這兩種技術的允許現象,並且超出了本手冊的範圍。

說明

一個對於可序列化交易隔離等級的請求會提供和這裡描述的完全一樣的行為。為了保持可序列化行為,現在應該請求可重複讀。

可序列化隔離等級

可序列化隔離等級提供了最嚴格的事務隔離。這個層級為所有已提交事務類比序列事務執行;就好像事務被按照序列一個接著另一個被執行,而不是並行地被執行。但是,和可重複讀層級相似,使用這個層級的應用必須準備好因為序列化失敗而重試事務。事實上,這個隔離等級完全像可重複讀一樣地工作,除了它會監視一些條件,這些條件可能導致一個可序列化事務的並發集合的執行產生的行為與這些事務所有可能的序列化(一次一個)執行不一致。這種監控不會引入超出可重複讀之外的阻塞,但是監控會產生一些負荷,並且對那些可能導致序列化異常的條件的檢測將觸發一次序列化失敗。例如,考慮一個表mytab,它初始時包含:

     class | value
    -------+-------
         1 |    10
         1 |    20
         2 |   100
         2 |   200

假設可序列化事務 A 計算:

    SELECT SUM(value) FROM mytab WHERE class = 1;

並且接著把結果(3)作為一個新行的value插入,新行的class`` = 2。同時,可序列化事務 B 計算:

    SELECT SUM(value) FROM mytab WHERE class = 2;

並得到結果 300,它會將其與class`` = 1插入到一個新行中。然後兩個事務都嘗試提交。如果其中一個事務運行在可重複讀隔離等級,兩者都被允許提交;但是由於沒有執行的序列化順序能在結果上一致,使用可序列化事務將允許一個事務提交並且將復原另一個並伴有這個訊息:

    ERROR:  could not serialize access due to read/write dependencies among transactions

這是因為,如果 A 在 B 之前執行,B 將計算得到合計值 330 而不是 300,而且相似地另一種順序將導致 A 計算出一個不同的合計值。當依賴可序列化事務來阻止異常時,重要的一點是任何從一個持久化使用者表讀出資料都不被認為是有效,直到讀它的事務已經成功提交為止。即便是對唯讀事務也是如此,除了在一個可延遲的唯讀事務中讀取的資料是讀出以後立刻有效,因為這樣的一個事務在開始讀取任何資料之前會等待,直到它能獲得一個快照保證來避免這種問題為止。在所有其他情況下,應用不能依靠在一個後來被中斷的事務中讀取的結果;相反,它們應當重試事務直到它成功。要保證真正的可序列化,本資料庫使用了謂詞鎖,這意味著它會保持鎖,這些鎖讓它能夠判斷在它先啟動並執行情況下,什麼時候一個寫操作會對一個並發事務中之前讀取的結果產生影響。在本資料庫中,這些鎖並不導致任何阻塞,並且因此會導致一個死結。它們被用來標識和標誌並發可序列化事務之間的依賴性,這些事務的組合可能導致序列化異常。相反,一個想要保證資料一致性的讀已提交或可重複讀事務可能需要拿走一個在整個表上的鎖,這可能阻塞其他嘗試使用該表的使用者,或者它可能會使用不僅會阻塞其他事務還會導致磁碟訪問的SELECT FOR UPDATESELECT FOR SHARE。像大部分其他資料庫系統,本資料庫中的謂詞鎖基於被一個事務真正訪問的資料。這些謂詞鎖將顯示在lockspg系統檢視表中,它們的modeSIReadLock。這種在一個查詢執行期間獲得的特別的鎖將依賴於該查詢所使用的計劃,並且在事務過程中多個細部鎖定(如元組鎖)可能和少量廣泛鎖定(如頁面鎖)相結合來防止耗盡用於跟蹤鎖的記憶體。如果一個READ ONLY事務檢測到不會有導致序列化異常的衝突發生,它可以在完成前釋放其 SIRead 鎖。事實上,READ ONLY事務將常常可以在啟動時確立這一事實並避免拿到任何謂詞鎖。如果你顯式地請求一個SERIALIZABLE READ ONLY DEFERRABLE事務,它將阻塞直到它能夠確立這一事實(這是唯一_一種可序列化事務阻塞但可重複讀事務不阻塞的情況)。在另一方面,SIRead 鎖常常需要被保持到事務提交之後,直到重疊的讀寫事務完成。堅持使用可序列化事務可以簡化開發。成功提交的並發可序列化事務的任意集合將得到和一次運行一個相同效果的這種保證意味著,如果你能證明一個單一事務在獨自運行時能做正確的事情,則你可以相信它在任何混合的可序列化事務中也能做正確的事情,即使它不知道那些其他事務做了些什麼,否則它將不會成功提交。重要的是使用這種技術的環境有一種普遍的方法來處理序列化失敗(總是會返回一個 SQLSTATE 值 '40001'),因為它將很難準確地預計哪些事務可能為讀/寫依賴性做貢獻並且需要被復原來阻止序列化異常。讀/寫依賴性的監控會產生開銷,如重啟被序列化失敗中止的事務,但是作為在該開銷和顯式鎖及SELECT FOR UPDATESELECT FOR SHARE導致的阻塞之間的一種平衡,可序列化事務是在某些環境中最好效能的選擇。雖然本資料庫的可序列化交易隔離等級只允許並發事務在能夠證明有一種串列執行能夠產生相同效果的前提下提交,但它卻不能總是阻止在真正的串列執行中不會發生的錯誤產生。尤其是可能會看到由於可序列化事務重疊執行導致的唯一約束被違背的情況,這些情況即便在嘗試插入鍵之前就顯式地檢查過該鍵不存在也會發生。避免這種問題的方法是,確保所有插入可能會衝突的鍵的可序列化事務首先顯式地檢查它們能不能那樣做。例如,試想一個要求使用者輸入新鍵的應用,它會通過嘗試查詢使用者給出的鍵來檢查鍵是否已經存在,或者是通過選取現有最大的鍵並且加一來產生一個新鍵。如果某些可序列化事務不遵循這種協議而直接插入新鍵,則也可能會報告唯一約束被違背,即便在並發事務串列執行的情況下不會發生唯一約束被違背也是如此。當依賴可序列化事務進行並發控制時,為了最佳效能應該考慮一下問題:

  • 在可能時聲明事務為READ ONLY

  • 控制活動串連的數量,如果需要使用一個串連池。這總是一個重要的效能考慮,但是在一個使用可序列化事務的繁忙系統中這尤為重要。

  • 只在一個單一事務中放完整性目的所需要的東西。

  • 不要讓串連不必要地“閑置在事務中”。配置參數 idle_in_transaction_session_timeout 可以被用來自動斷開拖延會話的串連。

  • 在那些由於使用可序列化事務自動提供的保護的地方消除不再需要的顯式鎖、SELECT FOR UPDATESELECT FOR SHARE

  • 當系統因為謂詞鎖表記憶體短缺而被強制結合多個頁面級謂詞鎖為一個單一的關係級謂詞鎖時,序列化失敗的比例可能會上升。你可以通過增加 max_pred_locks_per_transaction、max_pred_locks_per_relation 和 max_pred_locks_per_page 來避免這種情況。

  • 一次順序掃描將總是需要一個關係級謂詞鎖。這可能導致序列化失敗的比例上升。通過縮減 random_page_cost 和/或增加 cpu_tuple_cost 來鼓勵使用索引掃描將有助於此。一定要在交易回復和重啟數目的任何減少與查詢執行時間的任何全面改變之間進行權衡。

可序列化隔離等級是使用學術資料庫文獻中稱為可序列化快照隔離的技術實現的,通過添加序列化異常事務的檢查的方式構建在快照隔離的基礎之上。 與使用傳統鎖技術的其他系統相比,可以觀察到行為和效能方面的一些差異。