并發(fā)問題可歸納為以下幾類:
A.丟失更新:撤銷一個(gè)事務(wù)時(shí),把其他事務(wù)已提交的更新數(shù)據(jù)覆蓋(A和B事務(wù)并發(fā)執(zhí)行,A事務(wù)執(zhí)行更新后,提交;B事務(wù)在A事務(wù)更新后,B事務(wù)結(jié)束前也做了對(duì)該行數(shù)據(jù)的更新操作,然后回滾,則兩次更新操作都丟失了)。
B.臟讀:一個(gè)事務(wù)讀到另一個(gè)事務(wù)未提交的更新數(shù)據(jù)(A和B事務(wù)并發(fā)執(zhí)行,B事務(wù)執(zhí)行更新后,A事務(wù)查詢B事務(wù)沒有提交的數(shù)據(jù),B事務(wù)回滾,則A事務(wù)得到的數(shù)據(jù)不是數(shù)據(jù)庫中的真實(shí)數(shù)據(jù)。也就是臟數(shù)據(jù),即和數(shù)據(jù)庫中不一致的數(shù)據(jù))。
C.不可重復(fù)讀:一個(gè)事務(wù)讀到另一個(gè)事務(wù)已提交的更新數(shù)據(jù)(A和B事務(wù)并發(fā)執(zhí)行,A事務(wù)查詢數(shù)據(jù),然后B事務(wù)更新該數(shù)據(jù),A再次查詢?cè)摂?shù)據(jù)時(shí),發(fā)現(xiàn)該數(shù)據(jù)變化了)。
D. 覆蓋更新:這是不可重復(fù)讀中的特例,一個(gè)事務(wù)覆蓋另一個(gè)事務(wù)已提交的更新數(shù)據(jù)(即A事務(wù)更新數(shù)據(jù),然后B事務(wù)更新該數(shù)據(jù),A事務(wù)查詢發(fā)現(xiàn)自己更新的數(shù)據(jù)變了)。
E.虛讀(幻讀):一個(gè)事務(wù)讀到另一個(gè)事務(wù)已提交的新插入的數(shù)據(jù)(A和B事務(wù)并發(fā)執(zhí)行,A事務(wù)查詢數(shù)據(jù),B事務(wù)插入或者刪除數(shù)據(jù),A事務(wù)再次查詢發(fā)現(xiàn)結(jié)果集中有以前沒有的數(shù)據(jù)或者以前有的數(shù)據(jù)消失了)。
數(shù)據(jù)庫系統(tǒng)提供了四種事務(wù)隔離級(jí)別供用戶選擇:
A.Serializable(串行化):一個(gè)事務(wù)在執(zhí)行過程中完全看不到其他事務(wù)對(duì)數(shù)據(jù)庫所做的更新(事務(wù)執(zhí)行的時(shí)候不允許別的事務(wù)并發(fā)執(zhí)行。事務(wù)串行化執(zhí)行,事務(wù)只能一個(gè)接著一個(gè)地執(zhí)行,而不能并發(fā)執(zhí)行。)。
B.Repeatable Read(可重復(fù)讀):一個(gè)事務(wù)在執(zhí)行過程中可以看到其他事務(wù)已經(jīng)提交的新插入的記錄,但是不能看到其他其他事務(wù)對(duì)已有記錄的更新。
C.Read Commited(讀已提交數(shù)據(jù)):一個(gè)事務(wù)在執(zhí)行過程中可以看到其他事務(wù)已經(jīng)提交的新插入的記錄,而且能看到其他事務(wù)已經(jīng)提交的對(duì)已有記錄的更新。
D.Read Uncommitted(讀未提交數(shù)據(jù)):一個(gè)事務(wù)在執(zhí)行過程中可以看到其他事務(wù)沒有提交的新插入的記錄,而且能看到其他事務(wù)沒有提交的對(duì)已有記錄的更新。
| 丟失更新 | 臟讀 | 非重復(fù)讀 | 覆蓋更新 | 幻像讀 |
未提交讀 | Y | Y | Y | Y | Y |
已提交讀 | N | N | Y | Y | Y |
可重復(fù)讀 | N | N | N | N | Y |
串行化 | N | N | N | N | N |
隔離級(jí)別
數(shù)據(jù)庫系統(tǒng)有四個(gè)隔離級(jí)別(大多數(shù)數(shù)據(jù)庫默認(rèn)級(jí)別為read commited)。對(duì)數(shù)據(jù)庫使用何種隔離級(jí)別要審慎分析,因?yàn)?/span>
1. 維護(hù)一個(gè)最高的隔離級(jí)別雖然會(huì)防止數(shù)據(jù)的出錯(cuò),但是卻導(dǎo)致了并行度的損失,以及導(dǎo)致死鎖出現(xiàn)的可能性增加。
2. 然而,降低隔離級(jí)別,卻會(huì)引起一些難以發(fā)現(xiàn)的bug。
SERIALIZABLE(序列化)
添加范圍鎖(比如表鎖,頁鎖等,關(guān)于range lock,我也沒有很深入的研究),直到transaction A結(jié)束。以此阻止其它transaction B對(duì)此范圍內(nèi)的insert,update等操作。
幻讀,臟讀,不可重復(fù)讀等問題都不會(huì)發(fā)生。
REPEATABLE READ(可重復(fù)讀)
對(duì)于讀出的記錄,添加共享鎖直到transaction A結(jié)束。其它transaction B對(duì)這個(gè)記錄的試圖修改會(huì)一直等待直到transaction A結(jié)束。
可能發(fā)生的問題:當(dāng)執(zhí)行一個(gè)范圍查詢時(shí),可能會(huì)發(fā)生幻讀。
READ COMMITTED(提交讀)
在transaction A中讀取數(shù)據(jù)時(shí)對(duì)記錄添加共享鎖,但讀取結(jié)束立即釋放。其它transaction B對(duì)這個(gè)記錄的試圖修改會(huì)一直等待直到A中的讀取過程結(jié)束,而不需要整個(gè)transaction A的結(jié)束。所以,在transaction A的不同階段對(duì)同一記錄的讀取結(jié)果可能是不同的。
可能發(fā)生的問題:不可重復(fù)讀。
READ UNCOMMITTED(未提交讀)
不添加共享鎖。所以其它transaction B可以在transaction A對(duì)記錄的讀取過程中修改同一記錄,可能會(huì)導(dǎo)致A讀取的數(shù)據(jù)是一個(gè)被破壞的或者說不完整不正確的數(shù)據(jù)。
另外,在transaction A中可以讀取到transaction B(未提交)中修改的數(shù)據(jù)。比如transaction B對(duì)R記錄修改了,但未提交。此時(shí),在transaction A中讀取R記錄,讀出的是被B修改過的數(shù)據(jù)。
可能發(fā)生的問題:臟讀。
問題
我們看到,當(dāng)執(zhí)行不同的隔離級(jí)別時(shí),可能會(huì)發(fā)生各種各樣不同的問題。下面對(duì)它們進(jìn)行總結(jié)并舉例說明。
幻讀
幻讀發(fā)生在當(dāng)兩個(gè)完全相同的查詢執(zhí)行時(shí),第二次查詢所返回的結(jié)果集跟第一個(gè)查詢不相同。
發(fā)生的情況:沒有范圍鎖。
例子:
事務(wù)1 | 事務(wù)2 |
SELECT * FROM users WHERE age BETWEEN 10 AND 30 |
|
| INSERT INTO users VALUES ( 3 , 'Bob' , 27 ); COMMIT; |
SELECT * FROM users WHERE age BETWEEN 10 AND 30; |
如何避免:實(shí)行序列化隔離模式,在任何一個(gè)低級(jí)別的隔離中都可能會(huì)發(fā)生。
不可重復(fù)讀
在基于鎖的并行控制方法中,如果在執(zhí)行select時(shí)不添加讀鎖,就會(huì)發(fā)生不可重復(fù)讀問題。
在多版本并行控制機(jī)制中,當(dāng)一個(gè)遇到提交沖突的事務(wù)需要回退但卻被釋放時(shí),會(huì)發(fā)生不可重復(fù)讀問題。
事務(wù)1 | 事務(wù)2 |
SELECT * FROM users WHERE id = 1; |
|
| UPDATE users SET age = 21 WHERE id = 1 ; COMMIT; /* in multiversion concurrency*/ control, or lock-based READ COMMITTED * |
SELECT * FROM users WHERE id = 1; |
|
| COMMIT; /* lock-based REPEATABLE READ */ |
在上面這個(gè)例子中,事務(wù)2提交成功,它所做的修改已經(jīng)可見。然而,事務(wù)1已經(jīng)讀取了一個(gè)其它的值。在序列化和可重復(fù)讀的隔離級(jí)別中,數(shù)據(jù)庫管理系統(tǒng)會(huì)返回舊值,即在被事務(wù)2修改之前的值。在提交讀和未提交讀隔離級(jí)別下,可能會(huì)返回被更新的值,這就是“不可重復(fù)讀”。
有兩個(gè)策略可以防止這個(gè)問題的發(fā)生:
1. 推遲事務(wù)2的執(zhí)行,直至事務(wù)1提交或者回退。這種策略在使用鎖時(shí)應(yīng)用。(悲觀鎖機(jī)制,比如用select for update為數(shù)據(jù)行加上一個(gè)排他鎖)
2. 而在多版本并行控制中,事務(wù)2可以被先提交。而事務(wù)1,繼續(xù)執(zhí)行在舊版本的數(shù)據(jù)上。當(dāng)事務(wù)1終于嘗試提交時(shí),數(shù)據(jù)庫會(huì)檢驗(yàn)它的結(jié)果是否和事務(wù)1、事務(wù)2順序執(zhí)行時(shí)一樣。如果是,則事務(wù)1提交成功。如果不是,事務(wù)1會(huì)被回退。(樂觀鎖機(jī)制)
臟讀
臟讀發(fā)生在一個(gè)事務(wù)A讀取了被另一個(gè)事務(wù)B修改,但是還未提交的數(shù)據(jù)。假如B回退,則事務(wù)A讀取的是無效的數(shù)據(jù)。這跟不可重復(fù)讀類似,但是第二個(gè)事務(wù)不需要執(zhí)行提交。
事務(wù)1 | 事務(wù)2 |
SELECT * FROM users WHERE id = 1; |
|
| UPDATE users SET age = 21 WHERE id = 1 |
SELECT FROM users WHERE id = 1; |
|
| COMMIT; /* lock-based DIRTY READ */ |