再談數據庫事務隔離性

寫在前面

近兩年分佈式數據庫技術加速發展,而因爲金融行業技術生態的限制,周圍不少同窗對其並無深刻的瞭解,因此進行高性能、高可靠系統設計時每每缺乏這一利器。Ivan但願以系列文章的方式與你們交流探討,加深咱們對分佈式數據庫的認識。本文是該系列文章的第一篇,主要探討事務管理中的隔離性,釐清相關概念和關鍵技術,爲後面闡述分佈式數據庫的事務管理作一個鋪墊,姑且算是一篇前傳吧。html


正文

咱們首先從定義出發,事務管理包括原子性、一致性、隔離性和持久性四個方面,即ACID。全部數據庫專著都會給出這個四個特性的定義,本文咱們引用了Jim Gray對其的定義。git

Jim Gray是事務處理方面的大師,本文中不少內容都來自他的專著和論文。爲避免翻譯引入的歧義,這裏咱們直接引用原文。sql

Atomicity: Either all the changes from the transaction occur (writes, and messages sent), or none occur.
Consistency: The transaction preserves the integrity of stored information.
Isolation: Concurrently executing transactions see the stored information as if they were running serially (one after another).
Durability: Once a transaction commits, the changes it made (writes and messages sent) survive any system failures.數據庫

在上述隔離性(Isolation)的定義中,咱們能夠發現其目標是使併發事務的執行效果與串行一致,但在具體技術實現上每每須要在併發能力和串行化效果之間進行平衡,很難二者兼顧。平衡的結果就是會出現違反串行效果的現象即異常現象(Phenomenon)。一般來講,隔離級別的提高伴隨着併發能力的降低,二者負相關。各類數據庫在談到隔離級別時都會引用ANSI SQL-92標準隔離級別,咱們來看看它的具體內容。api

ANSI SQL-92 Isolation Levels

ANSI SQL-92多是最先提出了基於異常現象來定義隔離級別的方法,同時沒有將隔離級別與具體實現機制綁定,隔離的實現能夠基於鎖(lock-based)或者無鎖(lock-free),兼容了後續的技術發展。該標準根據三種異常現象將隔離性定義爲四個級別,具體以下。session

髒讀,事務(T1)中修改的數據項在還沒有提交的狀況下被其餘事務(T2)讀取到,而T1進行Rollback操做,則T2剛剛讀取到的數據並無實際存在。
不可重複讀,T1讀取數據項,T2對其中的數據進行了修改或刪除且Commit成功。若是T1嘗試再次讀取這些數據,會獲得T2修改後的數據或者發現數據已刪除。這樣T1在一個事務中兩次一樣條件的讀取,且結果集內容變動或結果集數量減小。
幻讀,T1使用特定的查詢條件得到一個結果集,T2插入新的數據且這些數據符合T2剛剛操做的查詢條件。T2 commit 成功後,T1再次執行一樣的查詢,此時獲得的結果集增大。併發

不少文章都結合數據庫產品對上述異常現象的實例和處理機制進行了說明,本文中再也不贅述,有興趣的同窗能夠參考文末的連接[1]。mvc

ANSI SQL-92標準早在92年發佈,但不管是當時仍是後來都沒有被各大數據庫廠商嚴格遵循,部分緣由多是標準過於簡化與實際應用有必定程度的脫離。Jim Gray等人在1995發佈了論文「A Critique of ANSI SQL Isolation Levels」 (本文中簡稱爲Critique[2])對隔離級別進行更全面的闡述,能夠幫助咱們加深理解。分佈式

Critique Isolation Levels

Critique提出了ANSI SQL-92存在的兩個問題,首先是天然語言方式界定的異常現象並不嚴格致使一些同質化的異常現象被遺漏;其次是一些典型的異常現象並無被涵蓋進去,致使隔離級別存在明顯缺失。所以,文中對ANSI SQL-92的三種異常現象(將其編號爲A1/A2/A3)進行了擴展(編號爲P1/P2/P3),並增長了另外5種常見的異常現象。受限於篇幅,這裏僅對兩種異常現象進行說明。post

Lost Update

丟失更新(Lost Update)是一個經典的數據庫問題,因爲太太重要全部主流數據庫都解決了該問題,咱們這裏將操做稍加變形來舉例。

咱們使用MySQL進行演示,建立表並初始化數據

create table account (balance int,name varchar(20)) ENGINE=InnoDB;
insert into account values(50,'Tom');
T1 T2
begin; begin;
select balance into @bal from account where name='Tom'
--------------------
@bal = 50
select balance into @bal from account where name='Tom'
-------------------
@bal = 50
update account set balance = @bal -40 where name = ‘Tom’;
commit;
update account set balance = @bal - 1 where name = ‘Tom’;
commit;

在上述操做中T一、T2串行執行效果是對餘額進行兩次扣減,分別爲40和1,最終值爲9,但並行的最終值爲49,T2的修改被丟失。咱們能夠發現Lost update的實質是T1事務讀取數據,然後該數據被T2事務修改並提交,T1基於已通過期的數據進行了再次修改,形成T2的修改被覆蓋。

Read Skew

讀偏序(Read Skew)是RC級遇到的問題。若是數據項x與y存在一致性約束,T1先對讀x,然後T2修改x和y後commit,此時T1再讀y。T1獲得的x與y不知足原有的一致性約束。

MySQL默認隔離級別爲RR,咱們須要手工設置爲RC並初始化數據

set session transaction isolation level read committed;
insert into account values(70,'Tom');
insert into account values(30,'Kevin');
T1 T2
begin; begin;
select * from account where name=’Tom’;
---------------------
balance name
70 Tom
select * from account where name=’Tom’;
---------------------
balance name
70 Tom
update account set balance = balance - 30 where name='Tom';
update account set balance = balance + 30 where name=’Kevin’;
commit;
select * from account where name='Kevin';
---------------------
balance name
60 Kevin
commit;

初始數據Tom與Kevin的帳戶合計爲100,在T1事務內的兩次讀取獲得帳戶合計爲130,顯然不符合以前的一致性約束。

補充這些異常現象後,Critique給出了新的矩陣,相比ANSI更加完善也更貼合真實的數據庫產品。

主流數據庫考慮到串行化效果與併發性能的平衡,通常默認隔離級別都介於RC與RR之間,部分提供了Serializable。特別提醒,不管ASNI SQL-92仍是Critique的隔離級別都不能確保直接映射到實際數據庫的同名隔離級別。

SI&MVCC

快照隔離(SI,Snapshot Isolation)是討論隔離性時常見的術語,能夠作兩種的解讀,一是具體的隔離級別,SQL Server、CockroachDB都直接定義了這個隔離級別;二是一種隔離機制用於實現相應的隔離級別,在Oracle、MySQL InnoDB、PostgreSQL等主流數據庫中廣泛使用。多版本併發控制(MVCC,multiversion concurrency control)是經過記錄數據項歷史版本的方式提高系統應對多事務訪問的併發處理能力,例如避免單值(Single-Valued)存儲狀況下寫操做對讀操做的鎖排斥。MVCC和鎖都是SI的重要實現手段,固然也存在無鎖的SI實現。如下是Critique描述的SI運做過程。

事務(記爲T1)開始的瞬間會獲取一個時間戳Start Timestamp(記爲ST),而數據庫內的全部數據項的每一個歷史版本都記錄着對應的時間戳Commit Timestamp(記爲CT)。T1讀取的快照由全部數據項版本中那些CT小於ST且最近的歷史版本構成,因爲這些數據項內容只是歷史版本不會再次被寫操做鎖定,因此不會發生讀寫衝突,快照內的讀操做永遠不會被阻塞。其餘事務在ST以後的修改,T1不可見。當T1 commit的瞬間會得到一個CT,並保證大於此刻數據庫中已存在的任意時間戳(ST或CT),持久化時會將這個CT將做爲數據項的版本時間戳。T1的寫操做也體如今T1的快照中,能夠被T1內的讀操做再次讀取。當T1 commit後,修改會對那些持有ST大於T1 CT的事務可見。
若是存在其餘事務(T2),其CT在T1的運行間隔【ST,CT】之間,與T1對一樣的數據項進行寫操做,則T1 abort,T2 commit成功,這個特性被稱爲First-committer-wins,能夠保證不出現Lost update。事實上,部分數據庫會將其調整爲First-write-wins,將衝突判斷提早到write操做時,減小衝突的代價。

這個過程不是某個數據庫的具體實現,事實上不一樣數據庫對於SI實現存在很大差異。例如,PostgreSQL會將歷史版本和當前版本一塊兒保存經過時間戳區分,而MySQL和Oracle都在回滾段中保存歷史版本。MySQL的RC與RR級別均使用了SI,若是當前事務(T1)讀操做的數據被其餘事務的寫操做加鎖,T1轉向回滾段讀取快照數據,避免讀操做被阻塞。可是RC的快照定義與以上描述不一樣,也包括了T1執行過程當中其餘事務提交的最新版本[6]。

此外,咱們還有一個重要發現,時間戳是生成SI的關鍵要素。在單機系統中,惟一時間戳比較容易實現,而對於分佈式系統在跨節點、跨數據中心甚至跨城市部署的狀況下如何創建一個惟一時鐘就成爲一個很是複雜的問題,咱們暫留下一個伏筆將在後面的專題文章中進行討論。

Serializable VS SSI

SI是如此有效,甚至在TPC-C benchmark測試中也沒有出現任何異常現象[5],但事實上SI不能保證完整的串行化效果。Critique中指出,SI還沒法處理A5B(Write Skew,寫偏序),以下圖所示。

Write Skew

寫偏序(Write Skew)也是一致性約束下的異常現象,即兩個並行事務都基於本身讀到的數據集去覆蓋另外一部分數據集,在串行化狀況下兩個事務不管何種前後順序,最終將達到一致狀態,但SI隔離級別下沒法實現。下圖的「黑白球」經常被用來講明寫偏序問題。

如何實現真正的串行化效果呢?事實上,早期的數據庫已經經過嚴格兩階段鎖協議(S2PL,Strict Two-Phase Locking)實現了徹底的串行化隔離(Serializable Isolation),即正在進行讀操做的數據阻塞對應寫操做,寫操做阻塞全部操做(包括讀操做和寫操做)。如阻塞形成循環將構成死鎖,則須要進行rollback操做。S2PL的問題顯而易見,在競爭激烈場景下,阻塞和死鎖會形成數據庫吞吐量降低和響應時間的增長,因此這種串行化沒法應用於實際生產環境。直到SSI的出現,人們終於找到具備實際價值的串行化隔離方案。

串行化快照隔離(SSI, Serializable Snapshot Isolation,也會被翻譯爲序列化快照)是基於SI改進達到Serializable級別的隔離性。SSI由Michael James Cahill在他的論文"Serializable Isolation for Snapshot Databases"[3]中提出(該論文得到2008 Sigmod Best Paper Award,文章末尾提供了該論文的2009年完整版[4]相關信息,有興趣的同窗能夠深刻研究)。SSI保留了SI的不少優勢,特別是讀不阻塞任何操做,寫不會阻塞讀。事務依然在快照中運行,但增長了對事務間讀寫衝突的監控用於識別事務圖(transaction graph)中的危險結構。當一組併發事務可能產生異常現象(anomaly),系統將經過回滾其中某些事務進行干預以消除anomaly發生的可能。這個過程雖然會致使某些事務的錯誤回滾(不會致使anomaly的事務被誤殺),但能夠確保消除anomaly[3]。

從理論模型看,SSI性能接近SI,遠遠好於S2PL。2012年,PostgreSQL在9.1版本中實現了SSI[7],可能也是首個支持SSI的商業數據庫,驗證了SSI的實現效果。CockroachDB也從Cahill的論文得到靈感,實現SSI並將其做爲其默認隔離級別。

隨着技術的發展,SI/SSI已經成爲主流數據庫的隔離技術,尤爲是後者的出現,無需開發人員在代碼經過顯式鎖來避免異常,從而下降了人爲錯誤的機率。在分佈式數據庫的相關章節中,咱們將進一步對SSI實現機制進行深刻探討。


參考文獻 [1]Innodb中的事務隔離級別和鎖的關係,ameng,https://tech.meituan.com/innodb-lock.html [2]H. Berenson, P. Bernstein, J. Gray, J.Melton, E. O’Neil,and P. O’Neil. A critique of ANSI SQL isolation levels. InProceedings of the SIGMOD International Conference on Management of Data, pages1–10, May 1995. [3]Michael J. Cahill, Uwe Röhm, and Alan D.Fekete. 2008. Serializable isolation for snapshot databases. In SIGMOD ’08:Proceedings of the 2008 ACM SIGMOD international conference on Management of data, pages 729–738, New York, NY, USA. ACM. [4]Michael James Cahill. 2009. Serializable Isolation for Snapshot Databases. Sydney Digital Theses. University of Sydney, School of Information Technologies [5] A. Fekete, D. Liarokapis, E. O’Neil, P.O’Neil, andD. Shasha. Making snapshot isolation serializable. In ACM transactions on database systems, volume 39(2), pages 492–528, June 2005. [6]姜承堯,MySQL技術內幕:InnoDB存儲引擎機, 械工業出版社, 2011 [7]https://wiki.postgresql.org/wiki/Serializable

相關文章
相關標籤/搜索