歡迎光臨
每天分享高質量文章

事務隔離級別和臟讀的快速入門

點擊上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

摘要: 原文出處 http://www.uml.org.cn/sjjm/201612052.asp 「Jonathan Allen」歡迎轉載,保留摘要,謝謝!

關鍵要點

僅從ACID或非ACID角度考慮問題是不夠的,你應知道你的資料庫支持何種事務隔離級別。

一些資料庫宣稱自己具有“最終一致性”,但卻可能對重覆查詢傳回不一致的結果。

相比於你所尋求的資料庫,一些資料庫提供更高的事務隔離級別。

臟讀可導致同一記錄得到兩個版本,或是完全地丟失一條記錄。

在同一事務中多次重新運行同一查詢後,可能會出現幻讀。

最近MongoDB登上了Reddit的頭條,因為MongoDB的核心開發者David Glasser痛苦地認識到MongoDB預設會執行臟讀。

在本文中,我們將解釋什麼是事務隔離級別和臟讀,並給出一些廣受歡迎的資料庫是如何實現它們的。

ANSI SQL給出了四種標準的事務隔離級別:可序列化(Serializable)、可重覆讀(Repeatable reads)、提交讀(Read committed)和未提交讀(Read uncommitted)。

許多資料庫預設是提交讀的,這保證了在事務運行期間用戶看不到轉變中的資料。提交讀的實現通過在讀取時暫時性地獲取鎖,並持有寫入鎖直至事務提交。

如果在一個事務中需要多次重覆同一讀取,並想要“合理地確定”所有的讀取總是會得到同樣的結果,這要在整個過程期間持有讀取鎖。在使用可重覆讀事務隔離級別時,上述操作是自動完成的。

我們這裡所說的“合理地確定”可重覆讀,是因為存在“幻讀”(phantom reads)的可能性。當執行使用了WHERE陳述句的查詢時,類似於“WHERE Status=1”,就有可能發生幻讀。雖然所涉及的行將被鎖上,但是這並不能阻止匹配WHERE條件的新行被添加進來。“幻”(phantom)一詞指在查詢第二次執行時所出現的行。

為確保在同一事務中的兩次讀取會傳回同樣的資料,可使用可序列化事務隔離級別。可序列化使用了“範圍鎖”,避免了匹配WHERE條件的新行添加到一個開放的事務中。

一般情況下,由於鎖競爭的存在,事務隔離級別越高,性能越差。因此為了改進讀取性能,一些資料庫還支持未提交讀。該事務隔離級別將無視鎖的存在(事實上其在SQL Server中被稱為“NOLOCK”),因此該級別下可執行臟讀。

臟讀所存在的問題

在探討臟讀問題之前,你必須要理解表並非是真實存在於資料庫中的,表只是一個邏輯結構。事實上你的資料是按一個或多個索引進行儲存的。主索引在大多數資料庫中被稱為“聚束索引”或“堆”(該術語在各NoSQL資料庫中各不相同)。因而當執行插入操作時,需要在每個索引中插入一行。當執行更新操作時,資料庫引擎僅需訪問指到被改變列的索引。但更新操作常常必須要在每個索引上執行兩個操作,即從舊的位置刪除併在新的位置插入。

在下圖中,你可看見一個普通的表,還有表中IX_Customer_State和PK_Customer物件更新操作的執行計劃。鑒於表的FullName列並未改變,所以可以跳過IX_Customer_FullName索引。

註意在SQL Server中,PK前綴指代主鍵,通常也是用於聚束索引的鍵。IX用於指代非聚束索引。其它的資料具有它們自己的命名規範。

解決了上述問題,讓我們看一下臟讀導致不一致資料的多種途徑。

未提交讀問題易於理解。在事務被完全提交之前,如果無視寫入鎖的存在,使用“未提交讀”的SELECT陳述句就可以就看到新插入或更新的行。如果這些轉變操作這時被回滾,從邏輯上說,SELECT操作將傳回並不存在的資料。

如果資料在更新操作過程中被移動了,這就產生了雙重讀取。例如,你正在讀取所有的客戶記錄的狀態。如果在你讀取“California”記錄和讀取“Texas”記錄之間,上面所說的更新陳述句被執行了,你就能看見“客戶1253”記錄兩次。一次是舊值,一次是新值。

記錄丟失發生的方式相同。如果我們提取“客戶1253”記錄並將其從“Texas”記錄移動到“Alaska”記錄,並再次使用狀態去選擇資料,你可能會完全地丟失該記錄。這就是發生在David Glasser的MongoDB資料庫中的事情。由於在更新操作期間讀取了索引,查詢丟失了記錄。

臟讀也會妨礙到排序操作,該問題的出現取決於資料庫的設計方式及特定的執行計劃。例如,臟讀可能發生於執行計劃對所有候選資料行採集指標信息時,如果在其後一行資料被更新了,但實際上執行引擎還是會使用已被採集的指標信息從原始位置拷貝資料。

快照隔離,或被稱為“行級版本控制”

為在避免臟讀問題的同時提供好的性能,許多資料庫支持快照隔離語意。運行於快照隔離狀態下,當前的事務不能看到任何先於其啟動的其它事務的結果。

快照隔離的實現是通過做被改變行的臨時拷貝,而非僅依靠於鎖機制,因此它也常被稱為“行級版本控制”。

很多支持快照隔離語意的資料庫在被請求使用“提交讀”事務隔離時,會自動使用快照隔離。

SQL Server中的事務隔離級別

SQL Server支持所有四種ANSI SQL事務隔離級別,外加一種顯式的快照隔離級別。提交讀可能也使用快照語意,這取決於資料庫中READ_COMMITTED_SNAPSHOT選項的配置方式。

在開關該選項前,你的資料庫需要做充分的測試。雖然提交讀可以提升讀取性能,但它也同時降低了寫入性能。尤其是tempdb被部署在慢速磁盤上時,因為這儲存了行的舊版本。

在SELECT陳述句中可以使用臭名昭著的NOLOCK指示符。NOLOCK的作用等同於將事務運行設置為未提交讀。這在SQL Server 2000及更早期的版本中被大量地使用,因為那時並沒有提供行級版本控制。儘管現在不再必要或不建議這樣做,但是該習慣仍然保留著。

更多信息參見“設置事務隔離級別 (Transact-SQL)”.

PostgreSQL中的事務隔離級別

雖然官方宣稱PostgreSQL支持所有四種ANSI事務隔離級別,但事實上PostgreSQL中只有三種事務隔離級別。每當查詢請求“未提交讀”時,PostgreSQL就默默地將其升級為“提交讀”。因此PostgreSQL不允許臟讀。

當你選取“未提交讀”級別時,事實上你得到了“提交讀”,在PostgreSQL對可重覆讀的實現中,臟讀是不可能發生的,因此實際的事務隔離級別可能比你所選取的要更加嚴格。這是被SQL標準所允許的,因為四種事務隔離級別僅定義了事務中一定不能發生的現象,它們並未定義應該發生哪種現象。

PostgreSQL並未顯式地提供快照隔離。當然快照隔離是在使用提交讀時自動發生的。這是因為PostgreSQL的設計從一開始就考慮了多版本併發控制。

在9.1版本之前,PostgreSQL不提供可序列化事務,會將它們靜默降級為可重覆讀。但當前所有仍在支持的PostgreSQL版本中都不再有這個限制了。

更多的信息參見PostgreSQL官方文件的13.2節,“ 事務隔離”.

MySQL中的事務隔離級別

InnoDB預設為可重覆讀,但是提供所有四種ANSI SQL事務隔離級別。提交讀使用快照隔離語意。

更多InnoDB相關的信息,參見MySQL官方文件的15.3.2.1節“ 事務隔離等級”

事務在使用MyISAM儲存引擎時是完全不被支持的,這裡使用了表一級的單一讀寫鎖(雖然在某些情況下,插入操作是可以繞過鎖的。)

Oracle中的事務隔離等級

Oracle只支持三種事務隔離級別,即提交讀、可序列化和只讀。在Oracle中,提交讀是預設的,它使用快照語意。

類似於PostgreSQL,Oracle並不提供未提交讀,永不允許臟讀。

可重覆讀並不在Oracle的支持串列中。如果你需要在Oracle中具有該行為,你的事務隔離級別需要被設置為可序列化。

只讀是Oracle所獨有的事務隔離級別。但是對此並沒有很好的文件,手冊中只有如下描述:

只讀事務只能看見那些在事務開始階段就被提交的改變,不允許INSERT、UPDATE和DELETE語言。

對其它兩種事務隔離級別的更多信息,參見Oracle官方文件第13章“資料併發和一致性”。

DB2中的事務隔離級別

DB2具有四種隔離級別,分別稱為可重覆讀、讀穩定性、游標穩定性和未提交讀。這四種級別並不與上述四種ANSI術語一一對應。

可重覆讀對應於ANSI SQL中的可序列化,意味著不可能存在臟讀。

讀穩定性對應於ANSI SQL中的可重覆讀。

游標穩定性用於提交讀,是DB2的預設設置配置。對於9.7版快照語意生效。而在9.7的前期版本中,DB2使用類似於SQL Server的鎖機制。

未提交讀在很大程度上類似於SQL Server中的未提交讀,也允許臟讀。手冊中推薦僅在只讀表上使用未提交讀,或是用在“可以看到未被其它應用提交的資料時”。

更多信息參見“事務隔離級別”。

MongoDB中的事務隔離級別

正如前文所提到的,MongoDB不支持事務。在其手冊中對此是這樣描述的:

因為在MongoDB中對單一文件的操作是原子的,兩階段提交只能提供類事務語意。在兩階段提交或回滾期間,應用可在中間點傳回中間資料。

事實上這意味著MongoDB使用臟讀語意,具有雙倍或丟失記錄的可能性。

CouchDB中的事務隔離等級

CouchDB也不支持事務。但是不同於MongoDB的是,它使用了多版本併發控制去避免臟讀。

讀取請求將總是在請求開始時就能看到資料庫的最新快照。

這所給予CouchDB的事務隔離等級,等價於具有快照語意的提交讀。

更多的信息參見“最終一致性”。

Couchbase Server的事務隔離級別

Couchbase Server常被混淆為CouchDB,但它是一種完全不同的產品。就索引而言,它並未提供任何形式的隔離。

當執行更新操作時,Couchbase Server僅更新主索引,或稱其為“真實的表”。所有的二級索引將被延遲更新。

雖然在Couchbase Server文件並沒有明確說明,看上去它在構建索引時使用了快照,如果確是如此,臟讀應該不成為問題。但是由於索引的延遲更新,在Couchbase Server中仍不能獲得真正的提交讀事務隔離級別。

和許多的NoSQL資料庫一樣,Couchbase Server並不直接支持事務。但是你確實可以使用顯式鎖,但鎖只能在被自動丟棄前維持30秒的時間。

更多的信息參見“對條目上鎖”、“你所應知道的關於Couchbase架構的所有事情”和“Couchbase視圖引擎的內幕”。

Cassandra中的事務隔離級別

Cassandra 1.0隔離了甚至是對一行的寫入操作。因為欄位是被逐一更新的,所以可以終止對舊值和新值混合在一起的記錄的讀取。

從1.1版本開始,Cassandra提供了“行級隔離”。這讓Cassandra具有等同於其它的資料庫中被稱為“未提交讀”的隔離級別。Cassandra並未提供更高級別的隔離。

更多的信息參見“關於事務和併發控制”。

瞭解你的資料庫的事務隔離級別

正如從上述實體中可看到的,僅從ACID和非ACID角度考慮你的資料庫是不夠的。你的確需要去知道你的資料庫應在何種情況下支持何種的事務隔離級別。




如果你對 Dubbo 感興趣,歡迎加入我的知識星球一起交流。

知識星球

目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:

01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽

05. 拓展機制 SPI

06. 執行緒池

07. 服務暴露 Export

08. 服務取用 Refer

09. 註冊中心 Registry

10. 動態編譯 Compile

11. 動態代理 Proxy

12. 服務呼叫 Invoke

13. 呼叫特性 

14. 過濾器 Filter

15. NIO 服務器

16. P2P 服務器

17. HTTP 服務器

18. 序列化 Serialization

19. 集群容錯 Cluster

20. 優雅停機

21. 日誌適配

22. 狀態檢查

23. 監控中心 Monitor

24. 管理中心 Admin

25. 運維命令 QOS

26. 鏈路追蹤 Tracing


一共 60 篇++

原始碼不易↓↓↓

點贊支持老艿艿↓↓

赞(0)

分享創造快樂