事務(wù)的ACID和四個(gè)隔離級(jí)別

在實(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

  1. 原子性(Atomicity):一個(gè)事務(wù)必須被視為一個(gè)不可分割的最小工作單元,整個(gè)事務(wù)中的所有操作要么全部提交成功,要么全部失敗回滾,對(duì)于一個(gè)事務(wù)來(lái)說(shuō),不可能只執(zhí)行其中的一部分操作。
  2. 一致性(Consistency):數(shù)據(jù)庫(kù)總是從一個(gè)一致性的狀態(tài)轉(zhuǎn)換到另一個(gè)一致性的狀態(tài)。
  3. 隔離性(Isolation):通常來(lái)說(shuō),一個(gè)事務(wù)所做的修改在最終提交以前,對(duì)其他事務(wù)是不可見(jiàn)的。針對(duì)不同的業(yè)務(wù)需求,隔離性分為4個(gè)級(jí)別:讀未提交、讀已提交、可重復(fù)讀、串行化。見(jiàn)下
  4. 持久性(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è)操作序列:

  1. 事務(wù)1開(kāi)始
  2. 事務(wù)1讀取目標(biāo)記錄
  3. 事務(wù)2開(kāi)始
  4. 事務(wù)2修改目標(biāo)記錄
  5. 事務(wù)1讀取目標(biāo)記錄
  6. 事務(wù)2回滾
  7. 事務(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è)操作序列:

  1. 事務(wù)1開(kāi)始
  2. 事務(wù)1讀取目標(biāo)記錄
  3. 事務(wù)2開(kāi)始
  4. 事務(wù)2修改目標(biāo)記錄
  5. 事務(wù)2提交
  6. 事務(wù)1修改目標(biāo)記錄
  7. 事務(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ù)讀可以使用兩種方法:

  1. 悲觀策略:串行化
  2. 樂(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è)操作序列,這次具體一些:

  1. 事務(wù)1開(kāi)始
  2. 事務(wù)1統(tǒng)計(jì)表內(nèi)數(shù)據(jù)的總行數(shù)
  3. 事務(wù)2開(kāi)始
  4. 事務(wù)2插入一條新紀(jì)錄
  5. 事務(wù)2提交
  6. 事務(wù)1利用“舊的總行數(shù)+新的數(shù)據(jù)表內(nèi)容”計(jì)算區(qū)分度
  7. 事務(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è)目的,但是必須保留本文的署名及鏈接。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容