在實(shí)際的業(yè)務(wù)場(chǎng)景中,并發(fā)讀寫(xiě)引出了事務(wù)控制的需求。主要關(guān)注事務(wù)的ACID和隔離性的4個(gè)級(jí)別。
ACID
事務(wù)指"一個(gè)被視為單一的工作單元的操作序列"。一個(gè)良好的事務(wù)處理系統(tǒng),必須具備四個(gè)標(biāo)準(zhǔn)特性,即ACID:
- 原子性(Atomicity):一個(gè)事務(wù)必須被視為一個(gè)不可分割的最小工作單元,整個(gè)事務(wù)中的所有操作要么全部提交成功,要么全部失敗回滾,對(duì)于一個(gè)事務(wù)來(lái)說(shuō),不可能只執(zhí)行其中的一部分操作。
- 一致性(Consistency):數(shù)據(jù)庫(kù)總是從一個(gè)一致性的狀態(tài)轉(zhuǎn)換到另一個(gè)一致性的狀態(tài)。
- 隔離性(Isolation):通常來(lái)說(shuō),一個(gè)事務(wù)所做的修改在最終提交以前,對(duì)其他事務(wù)是不可見(jiàn)的。針對(duì)不同的業(yè)務(wù)需求,隔離性分為4個(gè)級(jí)別:讀未提交、讀已提交、可重復(fù)讀、串行化。見(jiàn)下。
- 持久性(Durability):通常來(lái)說(shuō),一旦事務(wù)提交,則其所做的修改會(huì)永久保存到數(shù)據(jù)庫(kù)(即使系統(tǒng)崩潰,修改的數(shù)據(jù)也不會(huì)丟失)。針對(duì)不同的業(yè)務(wù)需求,持久性也分為多個(gè)級(jí)別,此處略。
隔離性的4個(gè)級(jí)別
在理解隔離性級(jí)別時(shí),很容易混淆“幻讀”與“不可重復(fù)讀”的問(wèn)題。這里先對(duì)4個(gè)隔離性級(jí)別給出概覽;然后分析原理,從實(shí)現(xiàn)角度理解各種問(wèn)題;最后作出總結(jié)。
概覽
關(guān)注隔離性的4個(gè)級(jí)別,包括讀未提交(Read Uncommitted)、讀已提交(Read Committed)、可重復(fù)讀(Repeatable Read)、可序列化(Serializable);及其對(duì)應(yīng)的問(wèn)題,包括臟讀(Dirty Read)、不可重復(fù)讀(Nonrepeatble Read)、幻讀(Phantom Read)。
讀未提交
讀未提交是數(shù)據(jù)庫(kù)應(yīng)保證的最低的隔離性級(jí)別:事務(wù)中的修改,即使沒(méi)有提交,對(duì)其他事務(wù)也都是可見(jiàn)的。
讀未提交面臨臟讀的問(wèn)題:事務(wù)可以讀取未提交的數(shù)據(jù),而該數(shù)據(jù)可能在未來(lái)因回滾而消失。從性能上來(lái)說(shuō),讀未提交不會(huì)比其他的級(jí)別好太多,但卻缺乏其他級(jí)別的很多好處。除非真的有非常必要的理由,在實(shí)際應(yīng)用中很少使用。
讀已提交
讀已提交滿足前面提到的隔離性的簡(jiǎn)單定義:一個(gè)事務(wù)所做的修改在最終提交以前,對(duì)其他事務(wù)是不可見(jiàn)的。換句話說(shuō),一旦提交,該事務(wù)所作的修改對(duì)其他正在進(jìn)行中的事務(wù)就是可見(jiàn)的。
狹義上,讀已提交解決了臟讀的問(wèn)題。這個(gè)級(jí)別有時(shí)候叫做不可重復(fù)讀,面臨不可重復(fù)讀的問(wèn)題:兩次執(zhí)行同樣的查詢,如果第二次讀到了其他事務(wù)提交的結(jié)果,則會(huì)得到不一樣的結(jié)果。
大多數(shù)數(shù)據(jù)庫(kù)的默認(rèn)隔離級(jí)別都是Read Committed,但MySQL不是。
可重復(fù)讀
在讀已提交的基礎(chǔ)上,可重復(fù)讀解決了部分不可重復(fù)的問(wèn)題:同一個(gè)事務(wù)中多次讀取同樣記錄結(jié)果是一致的。記錄指具體的數(shù)據(jù)行。
未能解決的那部分稱為幻讀:當(dāng)某個(gè)事務(wù)在讀取目標(biāo)范圍內(nèi)的記錄時(shí),另一個(gè)事務(wù)又在該范圍內(nèi)插入了新的記錄,當(dāng)之前的事務(wù)再次讀取該范圍的記錄時(shí),會(huì)產(chǎn)生第一次讀取范圍時(shí)不存在的幻行(Phantom Row)。需要注意的是,只有插入會(huì)產(chǎn)生幻行。
MySQL的默認(rèn)隔離級(jí)別是可重復(fù)讀,有幻讀問(wèn)題。
可序列化
可序列化是最高的隔離級(jí)別:強(qiáng)制事務(wù)序列化執(zhí)行。
可序列化解決了幻讀問(wèn)題。簡(jiǎn)單來(lái)說(shuō),可序列化會(huì)在目標(biāo)范圍加獨(dú)占鎖,將并發(fā)讀寫(xiě)相同范圍數(shù)據(jù)的請(qǐng)求序列化??尚蛄谢瘯?huì)導(dǎo)致大量的超時(shí)和鎖爭(zhēng)用問(wèn)題,因此,實(shí)際應(yīng)用中很少用到這個(gè)隔離級(jí)別,只有在非常需要確保數(shù)據(jù)的一致性而且可以接受沒(méi)有并發(fā)的情況下,才考慮采用該級(jí)別。
原理
討論基于鎖的并發(fā)控制中,4種隔離性級(jí)別的實(shí)現(xiàn)原理。重點(diǎn)關(guān)注讀鎖(read lock)、寫(xiě)鎖(write lock,或稱排它鎖)、范圍鎖(range lock)、鎖的持有時(shí)間等概念。
讀未提交
讀未提交的實(shí)現(xiàn):讀鎖、寫(xiě)鎖都在一個(gè)原子操作(如select、insert等)完成后立即釋放。換句話說(shuō),事務(wù)作出更新后,不管是否提交,由于已經(jīng)釋放了目標(biāo)記錄的寫(xiě)鎖,更新對(duì)其他事務(wù)就是可見(jiàn)的。
讀未提交存在臟讀問(wèn)題,假設(shè)操作序列:
- 事務(wù)1開(kāi)始
- 事務(wù)1讀取目標(biāo)記錄
- 事務(wù)2開(kāi)始
- 事務(wù)2修改目標(biāo)記錄
- 事務(wù)1讀取目標(biāo)記錄
- 事務(wù)2回滾
- 事務(wù)1提交
操作5中,事務(wù)1讀到了事務(wù)2修改但未提交的記錄,然后事務(wù)2回滾導(dǎo)致修改丟失,也就稱事務(wù)1讀到了“臟數(shù)據(jù)”,即臟讀。
區(qū)分目標(biāo)記錄與目標(biāo)范圍(見(jiàn)后文可重復(fù)讀的實(shí)現(xiàn)原理):
- 目標(biāo)記錄指一個(gè)具體的數(shù)據(jù)行;讀鎖、寫(xiě)鎖只針對(duì)目標(biāo)記錄。
- 目標(biāo)范圍指一個(gè)where語(yǔ)句描述的范圍;范圍鎖針對(duì)目標(biāo)范圍,見(jiàn)后。
讀已提交
出現(xiàn)臟讀的原因是寫(xiě)鎖的持有時(shí)間過(guò)短。讀已提交針對(duì)這一問(wèn)題作出了優(yōu)化:讀鎖仍然在一個(gè)原子操作完成后立即釋放;寫(xiě)鎖從寫(xiě)操作開(kāi)始持有,事務(wù)提交后釋放。事務(wù)作出更新前,會(huì)先申請(qǐng)目標(biāo)記錄的寫(xiě)鎖,并持續(xù)持有至事務(wù)提交后,釋放鎖后,更新對(duì)其他事務(wù)才是可見(jiàn)的。
對(duì)于讀未提交中的操作序列,操作5發(fā)生時(shí),由于事務(wù)2持有目標(biāo)記錄的寫(xiě)鎖,事務(wù)1會(huì)阻塞,直到事務(wù)2提交釋放該寫(xiě)鎖,解決了臟讀問(wèn)題。
讀已提交還存在不可重復(fù)讀問(wèn)題。假設(shè)操作序列:
- 事務(wù)1開(kāi)始
- 事務(wù)1讀取目標(biāo)記錄
- 事務(wù)2開(kāi)始
- 事務(wù)2修改目標(biāo)記錄
- 事務(wù)2提交
- 事務(wù)1修改目標(biāo)記錄
- 事務(wù)1提交
操作5完成后,事務(wù)2的修改對(duì)事務(wù)1可見(jiàn),從而操作6中,事務(wù)1會(huì)讀到修改,與操作2的結(jié)果不同,因此修改結(jié)果無(wú)法保證(如根據(jù)操作2讀取的結(jié)果做修改);但是事務(wù)1在此之前未對(duì)目標(biāo)記錄作出任何修改,因此事務(wù)1進(jìn)行操作6時(shí)的狀態(tài)理應(yīng)與操作2后一致(回顧事務(wù)的一致性要求)。以上即為不可重復(fù)讀。
不可重復(fù)讀與臟讀之間存在交叉。臟讀側(cè)重讀到不應(yīng)存在的數(shù)據(jù),不可重復(fù)讀強(qiáng)調(diào)兩次相同查詢的結(jié)果不一樣。實(shí)際上,可以將描述放寬到“目標(biāo)記錄的狀態(tài)不符合預(yù)期狀態(tài)”,如本應(yīng)該不同,卻讀到了相同。本質(zhì)上也是由于讀已提交實(shí)現(xiàn)原理導(dǎo)致的問(wèn)題。
可重復(fù)讀
解決不可重復(fù)讀可以使用兩種方法:
- 悲觀策略:串行化
- 樂(lè)觀策略:多版本 + 沖突檢測(cè)
悲觀策略:串行化
“串行化”不需要解釋,放棄并發(fā)、串行執(zhí)行當(dāng)然不存在任何問(wèn)題。
“串行化”的可重復(fù)讀實(shí)現(xiàn)是:讀鎖、寫(xiě)鎖從讀、寫(xiě)操作開(kāi)始持有,事務(wù)提交后釋放。與讀已提交的實(shí)現(xiàn)相比,可重復(fù)讀延長(zhǎng)讀鎖的持有時(shí)間直到事務(wù)提交后,在此期間,目標(biāo)記錄無(wú)法被修改。
對(duì)于讀已提交中的操作序列,操作2發(fā)生時(shí),事務(wù)1開(kāi)始持有目標(biāo)記錄的讀鎖,導(dǎo)致事務(wù)2的操作4會(huì)陷入阻塞,直到事務(wù)1提交釋放鎖。
“串行化”不同于“可序列化”。為了區(qū)分,前、后文中均將隔離性級(jí)別稱為“可序列化”,將此處的悲觀策略稱為“串行化”。
樂(lè)觀策略:多版本 + 沖突檢測(cè)
“多版本 + 沖突檢測(cè)”是更常見(jiàn)的實(shí)現(xiàn)方案:多個(gè)事務(wù)采用多個(gè)版本,最后提交時(shí)檢測(cè)是否與當(dāng)前數(shù)據(jù)版本沖突,如果沖突則報(bào)錯(cuò)提醒,否則成功提交。
“多版本 + 沖突檢測(cè)”的可重復(fù)讀實(shí)現(xiàn)是:事務(wù)開(kāi)始時(shí)持有當(dāng)前數(shù)據(jù)的快照,讀寫(xiě)均不沖突,提交時(shí)檢測(cè)修改的快照與當(dāng)前數(shù)據(jù)是否沖突。使用樂(lè)觀的沖突檢測(cè)策略代替悲觀的鎖策略,在中低程度的并發(fā)情況下性能更好。
對(duì)于讀已提交中的操作序列,事務(wù)1、2各自持有不同版本的快照,在操作4修改自己版本的目標(biāo)記錄后,操作5提交事務(wù)2,檢測(cè)不沖突(假設(shè)沒(méi)有其他事務(wù)),合并到當(dāng)前數(shù)據(jù),當(dāng)前數(shù)據(jù)完成修改;然后操作6繼續(xù)修改自己版本的目標(biāo)記錄,操作7提交事務(wù)1,發(fā)現(xiàn)與當(dāng)前數(shù)據(jù)沖突,給出報(bào)錯(cuò)。
幻讀問(wèn)題
幻讀是一種特殊的不可重復(fù)讀。
為什么會(huì)出現(xiàn)幻讀問(wèn)題呢?
Java的內(nèi)置鎖以對(duì)象為單位,RDBMS的鎖呢?前面的注釋中略有介紹。為了提高并發(fā)性能,簡(jiǎn)單的以數(shù)據(jù)表、數(shù)據(jù)庫(kù)為單位實(shí)現(xiàn)鎖的性能過(guò)低;標(biāo)準(zhǔn)SQL中,讀、寫(xiě)鎖以記錄(數(shù)據(jù)行)為單位,范圍鎖以范圍(邏輯上的范圍,用where描述)為單位。如果沒(méi)有范圍鎖,那么顯然讀、寫(xiě)鎖只能“鎖”在已存在的記錄上。假設(shè)操作序列,這次具體一些:
- 事務(wù)1開(kāi)始
- 事務(wù)1統(tǒng)計(jì)表內(nèi)數(shù)據(jù)的總行數(shù)
- 事務(wù)2開(kāi)始
- 事務(wù)2插入一條新紀(jì)錄
- 事務(wù)2提交
- 事務(wù)1利用“舊的總行數(shù)+新的數(shù)據(jù)表內(nèi)容”計(jì)算區(qū)分度
- 事務(wù)1提交
該操作序列是讀已提交中操作序列的一個(gè)具體實(shí)例。因此,可以解決部分不可重復(fù)讀問(wèn)題,不能解決的那部分就是幻讀了。
以基于鎖的“串行化”方案為例(“多版本+并發(fā)沖突”同理),假設(shè)不使用范圍鎖,則幻讀表現(xiàn)如下:由于事務(wù)2插入的記錄不獲取鎖,操作2獲取的讀鎖無(wú)法發(fā)揮作用,操作5提交事務(wù)2后,新記錄就對(duì)事務(wù)1可見(jiàn)了;操作6讀取時(shí),事務(wù)1認(rèn)為一致性依然滿足,便使用了舊的總行數(shù),并重新讀表計(jì)算distinct count,卻讀到了一條意料之外的新紀(jì)錄,破壞了一致性——好像出現(xiàn)了幻覺(jué)一樣,這條新紀(jì)錄就被稱為“幻行”,該現(xiàn)象即“幻讀”。
可序列化
對(duì)于基于鎖的“串行化”方案,可序列化實(shí)現(xiàn):從各操作開(kāi)始前持有讀鎖、寫(xiě)鎖、范圍鎖,直到事務(wù)提交后釋放。對(duì)于“多版本 + 沖突檢測(cè)”方案,可序列化基于更嚴(yán)格的寫(xiě)沖突檢測(cè)來(lái)實(shí)現(xiàn),詳見(jiàn)“快照隔離”技術(shù),此處不展開(kāi)。
范圍鎖如何解決幻讀問(wèn)題呢?
范圍鎖是一個(gè)邏輯概念上的鎖,事務(wù)從讀、寫(xiě)操作(帶顯式或隱式where)開(kāi)始前持有范圍鎖,直到事務(wù)提交后釋放。忽略讀、寫(xiě)鎖,對(duì)可重復(fù)讀中操作序列的影響如下:操作2中事務(wù)1獲取了目標(biāo)范圍上的范圍鎖,操作4發(fā)現(xiàn)目標(biāo)范圍被鎖,陷入阻塞,直到操作7事務(wù)提交。
隔離性級(jí)別的總結(jié)
各隔離級(jí)別解決了不同的問(wèn)題。"Y"說(shuō)明存在問(wèn)題,"-"說(shuō)明不存在:
| 隔離級(jí)別/問(wèn)題 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|
| 讀未提交 | Y | Y | Y |
| 讀已提交 | - | Y | Y |
| 可重復(fù)讀 | - | - | Y |
| 可序列化 | - | - | - |
在基于鎖的并發(fā)控制中,依靠不同的鎖持有時(shí)間實(shí)現(xiàn)各隔離級(jí)別。鎖均從操作前開(kāi)始持有,"S"表示操作結(jié)束后釋放,"C"表示事務(wù)提交后釋放:
| 隔離級(jí)別/問(wèn)題 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|
| 讀未提交 | S | S | S |
| 讀已提交 | C | S | S |
| 可重復(fù)讀 | C | C | S |
| 可序列化 | C | C | C |
MySQL的默認(rèn)隔離級(jí)別是可重復(fù)讀,解決了臟讀、部分不可重復(fù)讀問(wèn)題,有幻讀問(wèn)題。
參考:
本文鏈接:事務(wù)的ACID和四個(gè)隔離級(jí)別
作者:猴子007
出處:https://monkeysayhi.github.io
本文基于 知識(shí)共享署名-相同方式共享 4.0 國(guó)際許可協(xié)議發(fā)布,歡迎轉(zhuǎn)載,演繹或用于商業(yè)目的,但是必須保留本文的署名及鏈接。