數據庫事務ACID特性以及隔離等級

數據庫事務正確執行的4個基本要素是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。

ACID特性
原子性 整個事務中的所有操作,要麼全部完成,要麼全部不完成,不會停滯在中間某一個環節,假若事務在執行過程中發生了錯誤,那麼將會回滾到事務開始執行前的狀態,這個事務就像沒有被執行過一樣。
一致性 一個事務可以改變封裝的狀態。事務必須始終保持系統處於一致的狀態,不管任何給定的時間內併發的事務有多少。
隔離性 指兩個事務之間的隔離程度(稍後會說到)。
持久性 在事務完成以後,該事務對數據庫所做的更改會持久地保存在數據庫中,而且不會被回滾

關於隔離性:

隔離性涉及了多個事務的併發狀態,並且隔離性分爲幾個等級。當多個事務併發狀態時,就可能產生數據丟失更新的問題。一般情況可能存在兩類丟失更新。

假設某互聯網賬戶存在兩種消費形式,刷卡或者互聯網消費。一對夫婦同時使用這個賬戶進行消費,老公使用刷卡消費,老婆使用聯網消費,請看如下場景:

場景1:

第一類丟失更新
時刻     事務(1)老公    事務(2)老婆
T1 查詢餘額10000元 //do nothing
T2 //do nothing 查詢餘額10000元
T3 //do nothing 網購1000元
T4 請客喫飯1000元 //do nothing
T5 提交事務,餘額9000元 //do nothing
T6 //do nothing 取消購買,回滾事務至T2,餘額10000元

可以看出,整個事務的過程,老公花掉1000元,而老婆最後卻查詢剩餘10000元,明顯不符合事實。此類丟失更新,是由於兩個事務,一個提交成功,一個回滾,導致的數據不一致,可以成爲第一類丟失更新。(目前大多數數據庫已經消滅了這種問題),下面來看另一個場景。

場景2:

第二類事務丟失更新
時刻     事務(1)老公    事務(2)老婆
T1 查詢餘額10000元 //do nothing
T2 //do nothing 查詢餘額10000元
T3 //do nothing 網購1000元
T4 請客喫飯1000元 //do nothing
T5 提交事務,消費1000,餘額9000元 //do nothing
T6 //do nothing 提交成功,消費1000,餘額9000元

 可以看出,整個事務過程有兩筆消費分別是老公的和老婆的,但由於是在不同的事務中,事務之間無法互相干涉,所以最後查詢出的餘額都是9000元,不符合實際情況。這就是第二類丟失更新問題。

因此,爲了克服第二類丟失更新帶來的問題,也就是事務之間的協助的一致性,數據庫標準規範了事務之間的隔離等級,可以在一定程度上減少出現丟失更新帶來的問題。

隔離等級:

隔離等級可以在不同程度上減少丟失更新,隔離等級分爲4層,分別是:髒讀(dirty read)讀/寫提交(read commit)可重複讀寫(repeatable read)序列化(serilizable)。下面分別解釋這四個事務隔離等級。

1.髒讀:髒讀是最低的隔離級別,它允許一個事務去讀取另一個事務中未提交的數據,參考下列以髒讀爲隔離級別的例子

髒讀
時刻     事務(1)老公    事務(2)老婆 備註
T1 查詢餘額10000元 //do nothing XXX
T2 //do nothing 查詢餘額10000元 XXX
T3 //do nothing 網購1000元,餘額9000 XXX
T4 請客喫飯1000元,餘額8000 //do nothing 事務1讀取到事務2,餘額爲9000,再消費1000,餘額爲8000.
T5 提交事務,消費1000,餘額8000元 //do nothing 餘額依舊爲8000
T6 //do nothing 取消購買,回滾事務 此時回滾,餘額爲8000

 

可以清楚地看到, 其中只有1筆成功的消費,但是最後得到的結果是8000,其原因就是因爲髒讀,在T4時刻,事務1讀取了事務2的餘額,而在T6時刻,由於老婆回滾事務(類似第一類丟失更新,但是目前已經不存在這種問題,因此,此時的餘額爲8000)。

2.讀寫提交:針對以上髒讀所帶來的的一些問題,,因此SQL提出了第二個隔離級別,也就是讀/寫提交。讀寫提交就是一個事務只能讀取另一個事務已經提交的數據。例子:

讀/寫提交
時刻     事務(1)老公    事務(2)老婆 備註
T1 查詢餘額10000元 //do nothing XXX
T2 //do nothing 查詢餘額10000元 XXX
T3 //do nothing 網購1000元,餘額9000 XXX
T4 請客喫飯1000元,餘額9000 //do nothing 事務2的餘額還未提交,不能讀出,因此餘額爲9000
T5 提交事務,消費1000,餘額9000元 //do nothing 餘額依舊爲9000
T6 //do nothing 取消購買,回滾事務 此時回滾,餘額爲9000

可以看出,此例的事務採用讀寫提交的隔離級別,一共花費1筆,最後餘額爲9000,在T4時刻,由於事務2還未提交,所以事務1無法讀取到事務2的餘額。事務1在T5時刻提交,事務2在T6時刻回滾(並不會帶來第一類丟失更新問題,上面已經講到過)。所以餘額9000正確。但是也可能帶來另外一種問題,如下:

不可重複讀
時刻     事務(1)老公    事務(2)老婆 備註
T1 查詢餘額10000元 //do nothing XXX
T2 //do nothing 查詢餘額10000元 XXX
T3 //do nothing 網購1000元,餘額9000 XXX
T4 請客喫飯2000元,餘額8000 //do nothing 事務2的餘額還未提交,不能讀出,因此餘額爲10000-2000=8000
T5 //do nothing 繼續網購8000,餘額1000 餘額依舊爲9000
T6 //do nothing 提交訂單,提交事務 老婆提交事務,餘額爲更新爲1000
T7 提交事務發現,餘額爲1000,不足買單 //do nothing 事務1可以讀取到事務2已經提交的數據,並發現餘額爲1000

 可以看出,由於讀寫提交的隔離級別,在T6時刻事務2提交事務,餘額更新爲1000,但是事務1在T6時刻前不能讀取事務2的數據,所以,從事務1的這一列來看,假若自己是老公,查到餘額有10000元,於是和朋友去喫飯,結賬時被告知花了2000元,但是卻被提示餘額不足!其實問題就在於他的賬戶餘額的不能重複讀取導致的問題,這種場景就叫做不可重(復)讀。

3.可重複讀:爲了克服上面類似的不可重複讀帶來的問題,於是SQL標準提出了可重複讀的隔離級別來解決問題。可重複讀是針對數據庫同一條記錄而言,會使數據庫同一條記錄按照一個序列化進行操作。不會產生交叉的情況,因此可以保證數據的一致性。但是,在很多場景,數據庫需要同時對多條記錄進行讀寫操作,此時會產生下列情況:

幻讀
時刻     事務(1)老公    事務(2)老婆 備註
T1 //do nothing 查詢消費記錄:10條,準備打印 XXX
T2 開始消費,增加1條消費記錄 //do nothing XXX
T3 提交事務 //do nothing XXX
T4 //do nothing 共打印出11條消費記錄 事務2,打印得到11條結果,比查詢時多了1條,她會思考這一條信息的真實性,這樣的場景稱爲幻讀

 在T2-T3時刻,事務1開啓一筆消費,並提交,對於事務2,查詢到10條結果,但她並不知道事務1的操作,並得到11條記錄,於是便質疑多出的一條記錄是否多餘。產生幻讀。

4.序列化:爲了克服幻讀,SQL標準提出了序列化隔離級別,它是一種讓SQL按照順序讀寫的方式,能夠消除數據庫事務之間產生的數據不一致的問題。

最後,各類隔離級別以及可能產生的現象如下表:

      隔離級別 / 現象

髒讀 不可重讀 幻讀
髒讀
讀/寫提交 ×
可重複讀 × ×
序列化 × × ×

一般而言,從髒讀到序列化的隔離級別,系統性能直線下降,因此級別越高,會嚴重的抑制併發,導致大量線程被掛起,需要大量的時間恢復,一般建議使用讀寫提交的方式來設置事務,這樣不僅有助於高併發,也抑制了髒讀的產生。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章