事務隔離級別
事務併發執行時遇到的一致性問題
髒寫
如果一個事務修改了另一個為提交事務修改過的事務,就意味着發生了髒寫現象。我們可以把髒寫現象簡稱為P0.假設現在事務T1和T2併發執行,它們都要訪問數據項x(可以把數據項x當作一條記錄的某個字段)。那麼P0對應的操作執行序列如下所示:
p0: w1[x]...w2[x]...((c1 or a1) and (c2 or a2) in any order)
其中w1[x]表示事務T1修改了數據項x的值,w2[x]表示T2修改了數據項x的值,c1表示事務T1的提交(Commit),a1表示事務T1的中止(Abort),c2表示事務T2的提交,a2表示事務的中止,...表示其他的一些操作。從P0到操作執行序列中可以看出,事務T2修改了未提交事務T1修改過的數據,所以發生了髒寫現象。
髒寫現象可能引發一致性問題。比方説事務T1和T2要修改x和y這兩個數據項(修改不同的數據項就相當於修改不同記錄的字段),我們的一致性需求就是讓x的值和y的值始終相同。現在併發執行事務T1和T2,它們的操作執行序列如下所示:
w1[x=1]w2[x=2]w2[y=2]c2w1[y=1]c1
很顯然事務T2修改了尚未提交的事務T1的數據項x,此時發生了髒寫現象。如果我們允許髒寫現象的發生,那麼在T1和T2全部提交之後,x的值是2,而y的值卻是1,不符合 "x的值和y的值始終相同" 的一致性需求。
另外髒寫現象也可能破壞原子性和持久性。比如説有x和y這兩個數據項,它們初始的值都是0,兩個併發執行的事務T1和T2有下面的操作執行序列:
w1[x=2]w2[x=3]w2[y=3]c2a1
也就是T1先修改了數據項x,然後T2修改了數據項x和數據項y,然後T2提交,最後T1中止。現在的問題是T1中止時,需要將它對數據庫所做的修改回滾到該事務開啓時的樣子,也就是將數據項x的值修改為0。但是此時T2已經修改過數據項x並且提交了,如果要將T1回滾的話,相當於要對T2對數據庫所做的修改進行部分回滾(部分回滾是指T2只回滾對x做的修改,而不回滾對y做的修改),這就影響到了事務的原子性。如果要將T2對數據庫所做的修改全部回滾的話,那麼明明T2已經提交了,它對數據庫所做的修改應該具有持久性,怎麼能讓一個未提交的事務將T2的持久性破壞呢?所以這時候就會很尷尬。
髒讀
如果一個事務讀到了另一個未提交修改過的數據,就意味着發生了髒讀現象,我們可以把髒讀現象簡稱為P1。假設現在事務T1和T2併發執行,它們都要訪問數據項x。那麼P1對應的操作執行序列如下所示:
P1: w1[x]...r2[x]...((c1 or a1) and (c2 or a2) in any order)
髒讀現象也可能引發一致性問題。比如説事務T1和T2中要訪問x和y這兩個數據項,我們的一致性需求就是讓x的值和y的值始終相同,x和y的初始值都是0.現在併發執行事務T1和T2,它們的操作執行序列如下所示:
w1[x=1]r2[x=1]r2[y=0]c2w1[y=1]c1
很顯然T2是一個只讀事務,依次讀取x和y的值。可以由於T2讀取的數據項x是未提交事務T1修改過的值,所以導致最後讀取x的值為1,y的值為0。雖然最終數據庫狀態還是一致的(最終變為了x=1,y=1),但是T2卻得到了一個不一致的狀態。數據庫的不一致狀態是不應該暴露給用户的。
P1代表的事務的操作執行序列其實是一種髒讀的廣義解釋,針對髒讀還有一種嚴格節食。為了與廣義解釋進行區分,我們把髒讀的嚴格解釋稱為A1,A1對應的操作執行序列如下所示:
A1: w1[x]...r2[x]...(a1 and c2 in any order)
也就是T1先修改了數據項x的值,然後T2又讀取了未提交事務T1針對數據項x修改後的值,之後T1中止而T2提交。這就意味着T2讀取到了一個根本不存在的值,這也是髒讀的嚴格解釋。很顯然髒讀的廣義解釋是覆蓋嚴格解釋包含的範圍內的。
不可重複度
如果一個事務修改了另一個未提交事務讀取的數據,就意味着發生了不可重複度現象,或者叫模糊讀現象。我們可以把不可重複度現象簡稱為P2。假設現在事務T1和T2併發執行,它們都要訪問數據項x。那麼P2對應的操作執行序列如下所示:
r1[x=0]w2[x=1]w2[y=1]c2r1[y=1]c1
很顯然T1是一個只讀事務,依次讀取x和y的值。可是由於T1在讀取數據項x後,T2接着修改了數據項x和y的值,並且提交,之後T1再讀取數據項y。這個過程中雖未發生髒寫和髒讀(因為T1讀取y的值時,T2已經提交),但最終T1的到的x的值為0,y的值為1。很顯然這是一個不一致的狀態,這種不一致的狀態是不應該暴露給用户的。
P2代表的事務的操作執行序列其實是一種廣義解釋,針對不可重複度還有一種嚴格解釋。為了與廣義解釋進行區分,我們把不可重複讀的嚴格解釋稱為A2,A2對應的操作執行序列如下所示:
A2: r1[x]...w2[x]...c2...r1[x]...c1
也就是T1先讀取了數據項x的值,然後T2又修改了未提交事務T1讀區等數據項x的值,之後T2提交,然後T1再次讀區數據項x等值時會得到與第一次讀取時不同的值。這也是不可重複讀的嚴格解釋。很顯然不可重複度的廣義解釋是覆蓋嚴格解釋包含的範圍的。
幻讀
如果一個事務先根據某些搜索條件查詢出一些記錄,在該事務未提交時,另一個事務寫入了一些符合那些搜索條件的記錄(這裏的寫入可以指INSERT、DELETE、UPDATE操作),就意味着發生了幻讀現象。我們可以把幻讀現象簡稱為P3。假設現在事務T1和T2併發執行,那麼P3對應的操作執行序列如下所示:
P3: r1[p]...w2[y in P]...((c1 or a1) and (c2 or a2) any order)
其中r1[p]表示T1讀取一些符合搜索條件P的記錄,w2[y in p]表示T2寫入一些符合搜索條件p的記錄。
幻讀現象也可能引發一致性問題。比如説現在符合搜索條件的記錄條數有3條。我們有一個數據項z專門表示符合搜索條件P的記錄條數,它的初始值是3。我們的一致性需求就是讓z表示符合搜索條件P的記錄數。現在併發執行事務T1和T2,它們的操作執行序列如下所示:
r1[p]w2[insert y to p]r2[z=3]w2[z=4]c2r1[z=4]c1
T1先讀取符合搜索條件p的記錄,然後T2插入了一條符合搜索條件p的記錄,並且更新數據項z的值為4。然後T2提交,之後T1再讀取數據項z。z的值變為了4,這與T1之前實際讀取出的符合搜索條件p的記錄條數不合,不符合一致性需求。
P3代表的事務執行操作序列其實是一種廣義解釋,針對幻讀還有一種嚴格解釋。為了與廣義解釋進行區分,我們把幻讀的嚴格解釋稱為A3,A3對應的操作序列如下所示:
A3: r1[p]...w2[y in p]...c2...r1[p]...c1
也就是T1先讀取符合搜索條件p的記錄,然後T2寫入了符合搜索條件p的記錄。之後T1再讀取符合搜索條件p的記錄時,會發現兩次讀取的記錄是不一樣的。
SQL標準中的4種隔離級別
以上介紹了在併發事務執行過程中可能會遇到的一些現象,這些現象可能會對事務的一致性產生不同程度的影響。我們按照可能導致一致性問題的嚴重性給這些對象排一下序:
髒寫>髒讀>不可重複度>幻讀
在這裏,"捨棄一部分隔離性來換取一部分性能" 體現為:設立一些隔離級別,隔離級別越低,就越有可能發生越嚴重的問題。有一幫人(並不是設計MySQL的大叔)指定了一個SQL標準,在標準中設立了4個隔離級別。
- READ UNCOMMITTED:未提交讀
- READ COMMITTED:已提交讀
- REPEATABLE READ:可重複讀
- SERIALIZABL:可串行化
SQL標準中規定(是SQL標準規定,不是MySQL中規定):針對不同的隔離級別,併發事務執行過程中可以發生不同的現象,具體情況如下:
| 隔離級別 | 髒讀 | 不可重複度 | 幻讀 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABL | 不可能 | 不可能 | 不可能 |
也就是説
- 在READ UNCOMMITTED隔離級別下,可能發生髒讀、不和重複讀和幻讀現象;
- 在READ COMMITTED隔離級別下,可能發生不可重複讀和幻讀現象,但是不可能發生髒讀現象;
- 在REPEATABLE READ隔離級別下,可能發生幻讀現象,但是不可能發生髒讀和不可重複讀現象;
- 在SERIALIZABL隔離級別下,上述各種現象都不可能發生。
髒寫是怎麼回事?怎麼上面沒有提到?這是因為髒寫這個現象對一致性影響太嚴重了,無論是哪種隔離級別,都不允許髒寫的情況發生。
MySQL中支持的4種隔離級別
不同的數據庫廠商對SQL標準中規定的4種隔離級別的支持不太一樣。比如,Oracle就只支持READ COMMITTED和SERIALIZABLE隔離級別。MySQL雖然支持4種隔離級別,但與SQL標準中規定的各級隔離級別允許發生的現象有些出入————MySQL在REPEATBLE READ隔離級別下,可以很大程度上禁止幻讀現象的發生。
MySQL的默認隔離級別為REPEATABLE READ。
設置事務的隔離級別
如果我們想讓事務在不同的隔離級別中運行,可以通過下面的語句修改事務的隔離級別:
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL level;
其中,level有4個可選值:
level: {
READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABL
}