三: 事務(wù)

事務(wù)不是一個(gè)天然存在的東西,它是被人為創(chuàng)造出來的更卒,目的是簡化應(yīng)用層的編程模型。有了事務(wù)稚照,應(yīng)用程序不用考慮并發(fā)或各種錯(cuò)誤情況(進(jìn)程崩潰蹂空、網(wǎng)絡(luò)中斷、停電锐锣、磁盤問題)導(dǎo)致的各種不一致腌闯。
然而并非每個(gè)應(yīng)用程序都需要事務(wù)機(jī)制,有時(shí)可以弱化事務(wù)處理或完全放棄事務(wù)雕憔,一些安全相關(guān)的屬性也可能會(huì)避免引入事務(wù)姿骏。

我們需要判斷的是: 是否需要事務(wù)?需要什么樣的事務(wù)斤彼?

1. 什么是ACID

ACID代表原子性(Atomicity)分瘦,一致性(Consistency),隔離性(Isolation)和持久性(Durability)
BASE它代表基本可用性(Basically Available)琉苇,軟狀態(tài)(Soft State)和最終一致性(Eventual consistency),BASE唯一可以確定的是: "它不是ACID"嘲玫,此外它幾乎沒有承諾任何東西。

什么是原子性并扇?

一般來說去团,原子是指不能分解成小部分的東西。這個(gè)詞在計(jì)算的不同分支中意味著相似但又微妙不同的東西。例如土陪,在多線程編程中昼汗,如果一個(gè)線程執(zhí)行一個(gè)原子操作,這意味著另一個(gè)線程無法看到該操作的一半結(jié)果鬼雀。系統(tǒng)只能處于操作之前或操作之后的狀態(tài)顷窒,而不是介于兩者之間的狀態(tài)。相比之下源哩,ACID的原子性并是關(guān)于并發(fā)(concurrent)的鞋吉。它并不是在描述如果幾個(gè)進(jìn)程試圖同時(shí)訪問相同的數(shù)據(jù)會(huì)發(fā)生什么情況,這種情況包含在縮寫I 中励烦,即隔離性(Isolation)
ACID原子性的定義特征是:能夠在錯(cuò)誤時(shí)中止事務(wù)谓着,丟棄該事務(wù)進(jìn)行的所有寫入變更的能力。 或許 可中止性(abortability) 是更好的術(shù)語崩侠,但本書將繼續(xù)使用原子性漆魔,因?yàn)檫@是慣用詞。

什么是一致性却音?

  • 在主從復(fù)制中,我們討論了副本一致性矢炼,以及異步復(fù)制系統(tǒng)中的最終一致性問題系瓢。
  • 一致性哈希(Consistency Hash)是某些系統(tǒng)用于重新分區(qū)的一種分區(qū)方法。
  • 在CAP定理中句灌,一致性一詞用于表示可線性化
  • 在ACID的上下文中夷陋,一致性是指數(shù)據(jù)庫處于應(yīng)用程序所期待的“預(yù)期狀態(tài)”

可以看出ACID中的一致性本質(zhì)上要求應(yīng)用層來維持狀態(tài)一致,應(yīng)用程序有責(zé)任通過正確的定義事務(wù)來保持一致性胰锌。原子性骗绕,隔離性和持久性是數(shù)據(jù)庫的屬性,而一致性(在ACID意義上)是應(yīng)用程序的屬性资昧。應(yīng)用可能依賴數(shù)據(jù)庫的原子性和隔離屬性來實(shí)現(xiàn)一致性酬土,但這并不僅取決于數(shù)據(jù)庫。因此格带,字母C不屬于ACID撤缴。
(喬·海勒斯坦(Joe Hellerstein)指出,“ACID中的C”是被“扔進(jìn)去湊縮寫單詞的”叽唱,而且那時(shí)候大家都不怎么在乎一致性)

什么是隔離性屈呕?

ACID語義中的隔離性意味著,并發(fā)執(zhí)行的多個(gè)事務(wù)相互隔離棺亭,它們不能相互交叉虎眨。傳統(tǒng)的數(shù)據(jù)庫教科書將隔離性定義為可串行化(Serializability),這意味著每個(gè)事務(wù)可以假裝它是唯一在整個(gè)數(shù)據(jù)庫上運(yùn)行的事務(wù)。數(shù)據(jù)庫確保當(dāng)事務(wù)已經(jīng)提交時(shí)嗽桩,結(jié)果與它們按順序運(yùn)行(一個(gè)接一個(gè))完全相同钟鸵。

然而實(shí)踐中,由于性能問題涤躲,很少會(huì)使用串行化隔離棺耍。一些流行的數(shù)據(jù)庫如Oracle 11g,甚至沒有實(shí)現(xiàn)它种樱。在Oracle中有一個(gè)名為“可序列化”的隔離級(jí)別壳坪,但實(shí)際上它實(shí)現(xiàn)了一種叫做快照隔離(snapshot isolation) 的功能,這是一種比可序列化更弱的保證浸赫。

什么是持久性绘沉?
持久性是一個(gè)承諾,即一旦事務(wù)成功完成岂昭,即使發(fā)生硬件故障或數(shù)據(jù)庫崩潰以现,寫入的任何數(shù)據(jù)也不會(huì)丟失。

數(shù)據(jù)庫實(shí)現(xiàn)ACID主要是實(shí)現(xiàn): 1. 事務(wù); 2. 隔離性

2. 事務(wù): 實(shí)現(xiàn)原子性和持久性

(以InnoDB為例)

2.1 redo log

2.1.1 WAL機(jī)制保證持久

InnoDB使用redo log和undo log來實(shí)現(xiàn)事務(wù)约啊,其中redo log用于保證事務(wù)的持久性邑遏,undo log用于幫助事務(wù)回滾。redo log是順序讀寫恰矩,undo log需要隨機(jī)讀寫记盒。

image.png

采用WAL機(jī)制,將日志寫入Redo Log Buffer外傅,然后根據(jù)如下規(guī)則纪吮,將Buffer刷到磁盤:

  • 事務(wù)提交時(shí)
  • 當(dāng)log buffer中一半的內(nèi)存空間已經(jīng)被使用時(shí)
  • log checkpoint時(shí)

InnoDB Redo Log 是基于頁的:


image.png

image.png

2.1.2 LSN用于崩潰恢復(fù)

LSN(log sequence number),意思是日志序列號(hào)萎胰,在InnoDB中碾盟,LSN占8個(gè)字節(jié),并且單調(diào)遞增技竟,表示重做日志的字節(jié)總量冰肴。

LSN有三個(gè)含義:

  • 重做日志寫入的總量
  • checkpoint的位置,checkpoint表示刷新到磁盤的LSN
  • 頁的版本灵奖,在每個(gè)頁的頭部嚼沿,有一個(gè)FIL_PAGE_LSN,記錄該頁的LSN——該頁最后刷新時(shí)LSN大小瓷患,可用于崩潰恢復(fù)
image.png

2.2 undo log: 實(shí)現(xiàn)原子性

事務(wù)的原子性骡尽,需要依賴于回滾。這就是undo的作用——實(shí)現(xiàn)回滾(其實(shí)在InnoDB中還有實(shí)現(xiàn)MVCC的功能)
undo是一種邏輯日志擅编,取消之前的修改邏輯攀细。

那么undo log怎么實(shí)現(xiàn)回滾呢箫踩?
有兩種類型的undo log:

  • insert undo log
  • update undo log
image.png
  • next: 下一個(gè)undo log位置
  • type_cmpl: undo的類型
  • undo_no: 事務(wù)ID
  • table_id: 記錄undo log對應(yīng)的表對象

2.2.1 insert undo log

其中insert undo log是指在insert操作中產(chǎn)生的undo log,在業(yè)務(wù)上比較簡單谭贪。因?yàn)閕nsert操作的記錄境钟,只對事務(wù)本身可見,對其他事務(wù)不可見(這是事務(wù)隔離性的要求)俭识,故該undo log可以在事務(wù)提交后直接刪除慨削,不需要purge操作。

insert undo log記錄了所有主鍵的列和值套媚,在進(jìn)行rollback操作時(shí)缚态,根據(jù)這些值可以定位到具體的記錄,然后進(jìn)行刪除即可堤瘤。

2.2.2 update undo log

其復(fù)雜性來源于:


image.png

image.png

2.3 分布式事務(wù):兩階段提交

? 兩階段提交(two-phase commit)是一種用于實(shí)現(xiàn)多節(jié)點(diǎn)之間的原子事務(wù)提交的算法玫芦,即確保所有節(jié)點(diǎn)要么全部提交,要么全部中止本辐。 它是分布式數(shù)據(jù)庫中的經(jīng)典算法桥帆。


image.png

2PC使用單節(jié)點(diǎn)事務(wù)所沒有的新組件:協(xié)調(diào)者(coordinator)(也稱為事務(wù)管理器(transaction manager))。協(xié)調(diào)者通常在共享庫函數(shù)慎皱,但也可以是單獨(dú)的進(jìn)程或服務(wù)老虫。
?正常情況下,2PC事務(wù)以應(yīng)用在多個(gè)數(shù)據(jù)庫節(jié)點(diǎn)上讀寫數(shù)據(jù)開始宝冕。我們稱這些數(shù)據(jù)庫節(jié)點(diǎn)為參與者(participants)张遭。當(dāng)應(yīng)用準(zhǔn)備提交時(shí),協(xié)調(diào)者開始
階段1: 它發(fā)送一個(gè)準(zhǔn)備(prepare)請求到每個(gè)節(jié)點(diǎn)地梨,詢問它們是否能夠提交。然后協(xié)調(diào)者會(huì)跟蹤參與者的響應(yīng):

  • 如果所有參與者都回答“是”缔恳,表示它們已經(jīng)準(zhǔn)備好提交宝剖,那么協(xié)調(diào)者在階段 2 發(fā)出提交(commit)請求,然后提交真正發(fā)生歉甚。
  • 如果任意一個(gè)參與者回復(fù)了“否”万细,則協(xié)調(diào)者在階段2 中向所有節(jié)點(diǎn)發(fā)送中止(abort)請求。

2.3.1 2PC原理

  1. 當(dāng)應(yīng)用想要啟動(dòng)一個(gè)分布式事務(wù)時(shí)纸泄,它向協(xié)調(diào)者請求一個(gè)事務(wù)ID赖钞。此事務(wù)ID是全局唯一的。
  2. 應(yīng)用在每個(gè)參與者上啟動(dòng)單節(jié)點(diǎn)事務(wù)聘裁,并在單節(jié)點(diǎn)事務(wù)上捎帶上這個(gè)全局事務(wù)ID雪营。所有的讀寫都是在這些單節(jié)點(diǎn)事務(wù)中各自完成的。如果在這個(gè)階段出現(xiàn)任何問題(例如衡便,節(jié)點(diǎn)崩潰或請求超時(shí))献起,則協(xié)調(diào)者或任何參與者都可以安全中止洋访。
  3. 當(dāng)應(yīng)用準(zhǔn)備提交時(shí),協(xié)調(diào)者向所有參與者發(fā)送一個(gè)準(zhǔn)備請求谴餐,并打上全局事務(wù)ID的標(biāo)記姻政。如果任意一個(gè)請求失敗或超時(shí),則協(xié)調(diào)者向所有參與者發(fā)送針對該事務(wù)ID的中止請求岂嗓。
  4. 參與者收到準(zhǔn)備請求時(shí)汁展,需要確保在任意情況下都的確可以提交事務(wù)。這包括將所有事務(wù)數(shù)據(jù)寫入磁盤(出現(xiàn)故障厌殉,電源故障食绿,或硬盤空間不足都不能是稍后拒絕提交的理由)以及檢查是否存在任何沖突或違反約束。節(jié)點(diǎn)承諾年枕,只要向協(xié)調(diào)者回答“是”炫欺,這個(gè)事務(wù)一定可以不出差錯(cuò)地提交。
  5. 當(dāng)協(xié)調(diào)者收到所有準(zhǔn)備請求的答復(fù)時(shí)熏兄,會(huì)就提交或中止事務(wù)作出明確的決定(只有在所有參與者投贊成票的情況下才會(huì)提交)品洛。協(xié)調(diào)者必須把這個(gè)決定寫到磁盤上的事務(wù)日志中,以防止系統(tǒng)崩潰摩桶。這個(gè)時(shí)刻稱為提交點(diǎn)(commit point)桥状。
  6. 一旦協(xié)調(diào)者的決定落盤,提交或放棄請求會(huì)發(fā)送給所有參與者硝清。如果這個(gè)請求失敗或超時(shí)辅斟,協(xié)調(diào)者必須永遠(yuǎn)保持重試,直到成功為止芦拿。沒有回頭路:如果已經(jīng)做出決定士飒,不管需要多少次重試它都必須被執(zhí)行。如果參與者在此期間崩潰蔗崎,事務(wù)將在其恢復(fù)后提交——由于參與者投了贊成酵幕,因此恢復(fù)后它不能拒絕提交。

2.3.2 InnoDB的內(nèi)部XA事務(wù)

image.png

2.3.3 2PC vs 3PC

協(xié)調(diào)者可能發(fā)生故障缓苛。


image.png

(已經(jīng)對db2發(fā)送commit, 但是沒有對db2發(fā)送commit芳撒,此時(shí)協(xié)調(diào)者crash)

3PC解決了:

  • 同步阻塞問題
  • 單點(diǎn)故障問題,因?yàn)橐坏﹨⑴c者無法及時(shí)收到來自協(xié)調(diào)者的信息之后未桥,他會(huì)默認(rèn)執(zhí)行commit笔刹。而不會(huì)一直持有事務(wù)資源并處于阻塞狀態(tài)。

原文: https://zhuanlan.zhihu.com/p/35616810



  • 二階段提交的缺點(diǎn)

二階段提交看起來確實(shí)能夠提供原子性的操作冬耿,但是不幸的事舌菜,二階段提交還是有幾個(gè)缺點(diǎn)的:

image

1、同步阻塞問題淆党。
執(zhí)行過程中酷师,所有參與節(jié)點(diǎn)都是事務(wù)阻塞型的讶凉。當(dāng)參與者占有公共資源時(shí),其他第三方節(jié)點(diǎn)訪問公共資源不得不處于阻塞狀態(tài)山孔。也就是說從投票階段到提交階段完成這段時(shí)間懂讯,資源是被鎖住的。

2台颠、單點(diǎn)故障褐望。由于協(xié)調(diào)者的重要性,一旦協(xié)調(diào)者發(fā)生故障串前。參與者會(huì)一直阻塞下去瘫里。
尤其在第二階段,協(xié)調(diào)者發(fā)生故障荡碾,那么所有的參與者還都處于鎖定事務(wù)資源的狀態(tài)中谨读,而無法繼續(xù)完成事務(wù)操作。
【協(xié)調(diào)者發(fā)出Commmit消息之前宕機(jī)的情況】
(如果是協(xié)調(diào)者掛掉坛吁,可以重新選舉一個(gè)協(xié)調(diào)者劳殖,但是無法解決因?yàn)閰f(xié)調(diào)者宕機(jī)導(dǎo)致的參與者處于阻塞狀態(tài)的問題

3、數(shù)據(jù)不一致拨脉。在二階段提交的階段二中哆姻,當(dāng)協(xié)調(diào)者向參與者發(fā)送commit請求之后,發(fā)生了局部網(wǎng)絡(luò)異趁蛋颍或者在發(fā)送commit請求過程中協(xié)調(diào)者發(fā)生了故障矛缨,這回導(dǎo)致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之后就會(huì)執(zhí)行commit操作帖旨。但是其他部分未接到commit請求的機(jī)器則無法執(zhí)行事務(wù)提交箕昭。于是整個(gè)分布式系統(tǒng)便出現(xiàn)了數(shù)據(jù)不一致性的現(xiàn)象。

4解阅、二階段無法解決的問題:------ 極限情況下,對某一事務(wù)的不確定性盟广!
【協(xié)調(diào)者發(fā)出Commmit消息之后宕機(jī)的情況】
協(xié)調(diào)者再發(fā)出commit消息之后宕機(jī),而唯一接收到這條消息的參與者同時(shí)也宕機(jī)了瓮钥。那么即使協(xié)調(diào)者通過選舉協(xié)議產(chǎn)生了新的協(xié)調(diào)者,這條事務(wù)的狀態(tài)也是不確定的烹吵,沒人知道事務(wù)是否被已經(jīng)提交碉熄。

由于二階段提交存在著諸如同步阻塞、單點(diǎn)問題肋拔、腦裂等缺陷锈津,所以,研究者們在二階段提交的基礎(chǔ)上做了改進(jìn)凉蜂,提出了三階段提交琼梆。

3PC-三階段提交

三階段提交(Three-phase commit)性誉,也叫三階段提交協(xié)議(Three-phase commit protocol),是二階段提交(2PC)的改進(jìn)版本茎杂。

image

與兩階段提交不同的是错览,三階段提交有兩個(gè)改動(dòng)點(diǎn)。

1煌往、引入超時(shí)機(jī)制倾哺。同時(shí)在協(xié)調(diào)者和參與者中都引入超時(shí)機(jī)制。
2刽脖、在第一階段和第二階段中插入一個(gè)準(zhǔn)備階段羞海,保證了在最后提交階段之前各參與節(jié)點(diǎn)狀態(tài)的一致。

也就是說曲管,除了引入超時(shí)機(jī)制之外却邓,3PC把2PC的投票階段再次一分為二,這樣三階段提交就有CanCommit院水、PreCommit腊徙、DoCommit三個(gè)階段。

為什么要把投票階段一分為二衙耕?

假設(shè)有1個(gè)協(xié)調(diào)者昧穿,9個(gè)參與者。其中有一個(gè)參與者不具備執(zhí)行該事務(wù)的能力橙喘。
協(xié)調(diào)者發(fā)出prepare消息之后时鸵,其余參與者都將資源鎖住,執(zhí)行事務(wù)厅瞎,寫入undo和redo日志饰潜。
協(xié)調(diào)者收到相應(yīng)之后,發(fā)現(xiàn)有一個(gè)參與者不能參與和簸。所以彭雾,又出一個(gè)roolback消息。其余8個(gè)參與者锁保,又對消息進(jìn)行回滾薯酝。這樣子,是不是做了很多無用功爽柒?
所以吴菠,引入can-Commit階段,主要是為了在預(yù)執(zhí)行之前浩村,保證所有參與者都具備可執(zhí)行條件做葵,從而減少資源浪費(fèi)。

image
  • CanCommit階段

3PC的CanCommit階段其實(shí)和2PC的準(zhǔn)備階段很像心墅。協(xié)調(diào)者向參與者發(fā)送commit請求酿矢,參與者如果可以提交就返回Yes響應(yīng)榨乎,否則返回No響應(yīng)。

1.事務(wù)詢問 協(xié)調(diào)者向參與者發(fā)送CanCommit請求瘫筐。詢問是否可以執(zhí)行事務(wù)提交操作蜜暑。然后開始等待參與者的響應(yīng)。
2.響應(yīng)反饋 參與者接到CanCommit請求之后严肪,正常情況下史煎,如果其自身認(rèn)為可以順利執(zhí)行事務(wù),則返回Yes響應(yīng)驳糯,并進(jìn)入預(yù)備狀態(tài)篇梭。否則反饋No

  • PreCommit階段

本階段協(xié)調(diào)者會(huì)根據(jù)第一階段的詢盤結(jié)果采取相應(yīng)操作,詢盤結(jié)果主要有兩種:

情況1-假如協(xié)調(diào)者從所有的參與者獲得的反饋都是Yes響應(yīng)酝枢,那么就會(huì)執(zhí)行事務(wù)的預(yù)執(zhí)行:

1.發(fā)送預(yù)提交請求 協(xié)調(diào)者向參與者發(fā)送PreCommit請求恬偷,并進(jìn)入Prepared階段。
2.事務(wù)預(yù)提交 參與者接收到PreCommit請求后帘睦,會(huì)執(zhí)行事務(wù)操作袍患,并將undo和redo信息記錄到事務(wù)日志中。
3.響應(yīng)反饋 如果參與者成功的執(zhí)行了事務(wù)操作竣付,則返回ACK響應(yīng)诡延,同時(shí)開始等待最終指令。

情況2-假如有任何一個(gè)參與者向協(xié)調(diào)者發(fā)送了No響應(yīng)古胆,或者等待超時(shí)之后肆良,協(xié)調(diào)者都沒有接到參與者的響應(yīng),那么就執(zhí)行事務(wù)的中斷逸绎。具體步驟如下:

1.發(fā)送中斷請求 協(xié)調(diào)者向所有參與者發(fā)送abort請求惹恃。
2.中斷事務(wù) 參與者收到來自協(xié)調(diào)者的abort請求之后(或超時(shí)之后,仍未收到協(xié)調(diào)者的請求)棺牧,執(zhí)行事務(wù)的中斷巫糙。

image
  • doCommit階段

該階段進(jìn)行真正的事務(wù)提交,也可以分為以下兩種情況颊乘。

情況1-執(zhí)行提交

針對第一種情況参淹,協(xié)調(diào)者向各個(gè)參與者發(fā)起事務(wù)提交請求,具體步驟如下:

1. 協(xié)調(diào)者向所有參與者發(fā)送事務(wù)commit通知
2. 所有參與者在收到通知之后執(zhí)行commit操作乏悄,并釋放占有的資源
3. 參與者向協(xié)調(diào)者反饋事務(wù)提交結(jié)果

image

情況2-中斷事務(wù)

協(xié)調(diào)者沒有接收到參與者發(fā)送的ACK響應(yīng)(可能是接受者發(fā)送的不是ACK響應(yīng)承二,也可能響應(yīng)超時(shí)),那么就會(huì)執(zhí)行中斷事務(wù)纲爸。具體步驟如下:

  1. 發(fā)送中斷請求 協(xié)調(diào)者向所有參與者發(fā)送事務(wù)rollback通知。
  2. **事務(wù)回滾 **所有參與者在收到通知之后執(zhí)行rollback操作妆够,并釋放占有的資源识啦。
  3. **反饋結(jié)果 **參與者向協(xié)調(diào)者反饋事務(wù)提交結(jié)果负蚊。
  4. 中斷事務(wù) 協(xié)調(diào)者接收到參與者反饋的ACK消息之后,執(zhí)行事務(wù)的中斷颓哮。
image

2PC與3PC的區(qū)別

相對于2PC家妆,3PC主要解決的單點(diǎn)故障問題,并減少阻塞冕茅,因?yàn)橐坏﹨⑴c者無法及時(shí)收到來自協(xié)調(diào)者的信息之后伤极,他會(huì)默認(rèn)執(zhí)行commit。而不會(huì)一直持有事務(wù)資源并處于阻塞狀態(tài)姨伤。

【在doCommit階段哨坪,如果參與者無法及時(shí)接收到來自協(xié)調(diào)者的doCommit或者rebort請求時(shí),會(huì)在等待超時(shí)之后乍楚,會(huì)繼續(xù)進(jìn)行事務(wù)的提交当编。
其實(shí)這個(gè)應(yīng)該是基于概率來決定的,當(dāng)進(jìn)入第三階段時(shí)徒溪,說明參與者在第二階段已經(jīng)收到了PreCommit請求忿偷,那么Coordinator產(chǎn)生PreCommit請求的前提條件是他在第二階段開始之前,收到所有參與者的CanCommit響應(yīng)都是Yes臊泌。一旦參與者收到了PreCommit鲤桥,意味他知道大家其實(shí)都同意修改了。
所以渠概,一句話概括就是茶凳,當(dāng)進(jìn)入第三階段時(shí),由于網(wǎng)絡(luò)超時(shí)等原因高氮,雖然參與者沒有收到commit或者abort響應(yīng)慧妄,但是他有理由相信:成功提交的幾率很大。

但是這種機(jī)制也會(huì)導(dǎo)致數(shù)據(jù)一致性問題剪芍,因?yàn)槿停捎诰W(wǎng)絡(luò)原因,協(xié)調(diào)者發(fā)送的abort響應(yīng)沒有及時(shí)被參與者接收到罪裹,那么參與者在等待超時(shí)之后執(zhí)行了commit操作饱普。這樣就和其他接到abort命令并執(zhí)行回滾的參與者之間存在數(shù)據(jù)不一致的情況。

【如果進(jìn)入PreCommit后状共,協(xié)調(diào)者發(fā)出的是abort請求套耕,如果只有一個(gè)參與者收到并進(jìn)行了abort操作,而其他對于系統(tǒng)狀態(tài)未知的參與者會(huì)根據(jù)3PC選擇繼續(xù)Commit峡继,那么系統(tǒng)的不一致性就存在了冯袍。所以無論是2PC還是3PC都存在問題,后面會(huì)繼續(xù)了解傳說中唯一的一致性算法Paxos】捣撸】



3. MVCC&鎖: 隔離性

實(shí)現(xiàn)隔離并沒有想象中那么簡單儡循,可串行化的隔離會(huì)嚴(yán)重影響性能,而許多數(shù)據(jù)庫卻不愿意犧牲性能征冷,因此择膝,系統(tǒng)通常使用較弱的隔離級(jí)別,它可以防止某些單并不是全部的并發(fā)問題检激。這些隔離級(jí)別難以理解肴捉,并且會(huì)帶來難以捉摸的隱患,但是它們?nèi)匀辉趯?shí)踐中被使用叔收。

3.1 sql隔離級(jí)別

SQL 標(biāo)準(zhǔn)的事務(wù)隔離級(jí)別包括:讀未提交(read uncommitted)齿穗、讀提交(read committed)、可重復(fù)讀(repeatable read)和串行化(serializable ):

  • 讀未提交是指今穿,一個(gè)事務(wù)還沒提交時(shí)缤灵,它做的變更就能被別的事務(wù)看到。
  • 讀提交是指蓝晒,一個(gè)事務(wù)提交之后腮出,它做的變更才會(huì)被其他事務(wù)看到。
  • 可重復(fù)讀是指芝薇,一個(gè)事務(wù)執(zhí)行過程中看到的數(shù)據(jù)胚嘲,總是跟這個(gè)事務(wù)在啟動(dòng)時(shí)看到的數(shù)據(jù)是一致的。當(dāng)然在可重復(fù)讀隔離級(jí)別下洛二,未提交變更對其他事務(wù)也是不可見的馋劈。
  • 串行化,顧名思義是對于同一行記錄晾嘶,“寫”會(huì)加“寫鎖”妓雾,“讀”會(huì)加“讀鎖”。當(dāng)出現(xiàn)讀寫鎖沖突的時(shí)候垒迂,后訪問的事務(wù)必須等前一個(gè)事務(wù)執(zhí)行完成械姻,才能繼續(xù)執(zhí)行。
image.png

不同隔離級(jí)別帶來的問題

  • 更新丟失(Lost Update):當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行机断,然后基于最初選定的值更新該行時(shí)楷拳,由于每個(gè)事務(wù)都不知道其他事務(wù)的存在,就會(huì)發(fā)生丟失更新問題--最后的更新覆蓋了由其他事務(wù)所做的更新吏奸。例如欢揖,兩個(gè)編輯人員制作了同一文檔的電子副本。每個(gè)編輯人員獨(dú)立地更改其副本奋蔚,然后保存更改后的副本她混,這樣就覆蓋了原始文檔。最后保存其更改副本的編輯人員覆蓋另一個(gè)編輯人員所做的更改。如果在一個(gè)編輯人員完成并提交事務(wù)之前产上,另一個(gè)編輯人員不能訪問同一文件棵磷,則可避免此問題。
  • 臟讀(Dirty Reads):一個(gè)事務(wù)正在對一條記錄做修改晋涣,在這個(gè)事務(wù)完成并提交前,這條記錄的數(shù)據(jù)就處于不一致狀態(tài)沉桌;這時(shí)谢鹊,另一個(gè)事務(wù)也來讀取同一條記錄,如果不加控制留凭,第二個(gè)事務(wù)讀取了這些“臟”數(shù)據(jù)佃扼,并據(jù)此做進(jìn)一步的處理,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系蔼夜。這種現(xiàn)象被形象地叫做”臟讀”兼耀。
  • 臟寫: 當(dāng)兩個(gè)事務(wù)同時(shí)嘗試去更新某一條數(shù)據(jù)記錄時(shí),就肯定會(huì)存在一個(gè)先一個(gè)后求冷。而當(dāng)事務(wù)A更新時(shí)瘤运,事務(wù)A還沒提交,事務(wù)B就也過來進(jìn)行更新匠题,覆蓋了事務(wù)A提交的更新數(shù)據(jù)拯坟,這就是臟寫。臟寫是更新丟失的一種韭山。
  • 不可重復(fù)讀(Non-Repeatable Reads):一個(gè)事務(wù)在讀取某些數(shù)據(jù)后的某個(gè)時(shí)間郁季,再次讀取以前讀過的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變钱磅、或某些記錄已經(jīng)被刪除了梦裂!這種現(xiàn)象就叫做“不可重復(fù)讀”。
  • 幻讀(Phantom Reads):一個(gè)事務(wù)按相同的查詢條件重新讀取以前檢索過的數(shù)據(jù)盖淡,卻發(fā)現(xiàn)其他事務(wù)插入了滿足其查詢條件的新數(shù)據(jù)年柠,這種現(xiàn)象就稱為“幻讀”。

3.2 MVCC

3.2.1 實(shí)現(xiàn)讀已提交

最常見的情況是禁舷,數(shù)據(jù)庫通過使用行鎖(row-level lock) 來防止臟寫:當(dāng)事務(wù)想要修改特定對象(行或文檔)時(shí)彪杉,它必須首先獲得該對象的鎖。然后必須持有該鎖直到事務(wù)被提交或中止牵咙。給定時(shí)刻派近,只有一個(gè)事務(wù)可持有給定對象的鎖;如果另一個(gè)事務(wù)要寫入同一個(gè)對象洁桌,則必須等到第一個(gè)事務(wù)提交或中止后渴丸,才能獲取該鎖并繼續(xù)。這種鎖定是處于讀已提交模式(或更強(qiáng)的隔離級(jí)別)的數(shù)據(jù)庫自動(dòng)完成的。

如何防止臟讀谱轨?一種選擇是使用相同的鎖戒幔,并要求任何想要讀取對象的事務(wù)來簡單地獲取該鎖,然后在讀取之后立即再次釋放該鎖土童。這能確保不會(huì)讀取進(jìn)行時(shí)诗茎,對象不會(huì)在臟的狀態(tài),有未提交的值(因?yàn)樵谀嵌螘r(shí)間鎖會(huì)被寫入該對象的事務(wù)持有)献汗。

但是讀鎖的方式在實(shí)際中并不可行敢订。因?yàn)檫\(yùn)行時(shí)間較長的寫事務(wù)會(huì)迫使許多只讀事務(wù)等待太長時(shí)間,這會(huì)嚴(yán)重影響只讀事務(wù)的響應(yīng)延遲罢吃。并且于可操作性差:因?yàn)榈却x鎖楚午,應(yīng)用某個(gè)部分的遲緩導(dǎo)致其他部分出現(xiàn)問題,產(chǎn)生連鎖效應(yīng)尿招。

出于這個(gè)原因矾柜,大多數(shù)數(shù)據(jù)庫如下方式防止臟讀:對于每個(gè)待更新的對象,數(shù)據(jù)庫會(huì)維護(hù)其舊值和當(dāng)前持有鎖事務(wù)將要設(shè)置的新值兩個(gè)版本就谜。事務(wù)提交之前怪蔑,任何其他讀取對象的事務(wù)都會(huì)拿到舊值。 只有當(dāng)寫事務(wù)提交后吁伺,才能切換到讀取新值饮睬。

3.2.2 解決讀傾斜

不可重復(fù)讀(nonrepeatable read)又稱為讀傾斜(read skew),此處傾斜的意思是:時(shí)間異常篮奄。

快照級(jí)別隔離(snapshot isolation)是這個(gè)問題最常見的解決方案捆愁。想法是,每個(gè)事務(wù)都從數(shù)據(jù)庫的一致快照(consistent snapshot) 中讀取——也就是說窟却,事務(wù)可以看到事務(wù)開始時(shí)在數(shù)據(jù)庫中提交的所有數(shù)據(jù)昼丑。即使這些數(shù)據(jù)隨后被另一個(gè)事務(wù)更改,每個(gè)事務(wù)也只能看到該特定時(shí)間點(diǎn)的舊數(shù)據(jù)夸赫。

快照隔離對長時(shí)間運(yùn)行的只讀查詢(如備份和分析)非常有用菩帝。如果查詢的數(shù)據(jù)在查詢執(zhí)行的同時(shí)發(fā)生變化,則很難理解查詢的含義茬腿。當(dāng)一個(gè)事務(wù)可以看到數(shù)據(jù)庫在某個(gè)特定時(shí)間點(diǎn)凍結(jié)時(shí)的一致快照呼奢,理解起來就很容易了。

3.2.3 讀已提交 vs 可重復(fù)讀

快照級(jí)別隔離的實(shí)現(xiàn)是采用了比讀已提交更加通用的機(jī)制切平∥沾。考慮到多個(gè)正在進(jìn)行的事務(wù)可能在不同的時(shí)間點(diǎn)查看數(shù)據(jù)庫狀態(tài),數(shù)據(jù)庫必須可能保留一個(gè)對象的多個(gè)不同的提交版本悴品,所以這種技術(shù)被稱為多版本并發(fā)控制(MVCC, multi-version concurrentcy control)禀综。

如果一個(gè)數(shù)據(jù)庫只需要提供讀已提交的隔離級(jí)別简烘,而不提供快級(jí)別照隔離,那么保留一個(gè)對象的兩個(gè)版本就足夠了:提交的版本和尚未提交的版本定枷。支持快照隔離的存儲(chǔ)引擎通常也使用MVCC來實(shí)現(xiàn)讀已提交隔離級(jí)別孤澎。一種典型的方法讀已提交為每個(gè)查詢使用單獨(dú)的快照,而快照隔離對整個(gè)事務(wù)使用相同的快照欠窒。

3.2.4 MVCC實(shí)現(xiàn)——解決只讀事務(wù)

image.png

重點(diǎn):

  1. 當(dāng)一個(gè)事務(wù)開始時(shí)覆旭,它被賦予一個(gè)唯一的,永遠(yuǎn)增長的事務(wù)ID(txid)岖妄。每當(dāng)事務(wù)向數(shù)據(jù)庫寫入任何內(nèi)容時(shí)姐扮,它所寫入的數(shù)據(jù)都會(huì)被標(biāo)記上寫入者的事務(wù)ID。
  2. 表中的每一行都有一個(gè) created_by 字段衣吠,其中包含將該行插入到表中的的事務(wù)ID。此外壤靶,每行都有一個(gè) deleted_by 字段缚俏,最初是空的。如果某個(gè)事務(wù)刪除了一行贮乳,那么該行實(shí)際上并未從數(shù)據(jù)庫中刪除忧换,而是通過將 deleted_by 字段設(shè)置為請求刪除的事務(wù)的ID來標(biāo)記為刪除。在稍后的時(shí)間向拆,當(dāng)確定沒有事務(wù)可以再訪問已刪除的數(shù)據(jù)時(shí)亚茬,數(shù)據(jù)庫中的垃圾收集過程會(huì)將所有帶有刪除標(biāo)記的行移除,并釋放其空間浓恳。
  3. UPDATE 操作在內(nèi)部翻譯為 DELETEINSERT 刹缝。例如,上圖中颈将,事務(wù)13 從賬戶2 中扣除100美元梢夯,將余額從500美元改為400美元。實(shí)際上包含兩條賬戶2 的記錄:余額為500 的行被標(biāo)記為被事務(wù)13刪除晴圾,余額為 400 的行由事務(wù)13創(chuàng)建颂砸。

事務(wù)讀取數(shù)據(jù)庫時(shí)的可見性規(guī)則:

  1. 在每次事務(wù)開始時(shí),數(shù)據(jù)庫列出當(dāng)時(shí)所有當(dāng)時(shí)正在進(jìn)行中(尚未提交或中止)的事務(wù)清單死姚,然后忽略這些事務(wù)完成的部分寫入(盡管之后可能被提交)人乓,即不可見
  2. 所有中止事務(wù)所執(zhí)行的任何寫入都不可見
  3. 較晚事務(wù)ID(即,晚于當(dāng)前事務(wù))所做的任何寫入都被忽略都毒,而不管這些事務(wù)是否已經(jīng)提交针饥。
  4. 除此之外诊杆,所有其他的寫入都應(yīng)用查詢可見。

這些規(guī)則適用于創(chuàng)建和刪除對象。在上圖中,當(dāng)事務(wù)12 從賬戶2 讀取時(shí)疾渣,它會(huì)看到 500 的余額,因?yàn)?500 余額的刪除是由事務(wù)13 完成的(根據(jù)規(guī)則3,事務(wù)12 看不到事務(wù)13 執(zhí)行的刪除)竟终,且400美元記錄的創(chuàng)建也是不可見的(按照相同的規(guī)則)。

換句話說切蟋,如果以下兩個(gè)條件都成立统捶,則可見一個(gè)對象:

  • 讀事務(wù)開始時(shí),創(chuàng)建該對象的事務(wù)已經(jīng)提交柄粹。
  • 對象未被標(biāo)記為刪除喘鸟,或即使被標(biāo)記為刪除,刪除事務(wù)在當(dāng)前事務(wù)開始時(shí)尚未提交驻右。

MVCC with索引

索引如何在多版本數(shù)據(jù)庫中工作什黑?

  • 一種選擇是使索引簡單地指向?qū)ο蟮乃邪姹荆⑶倚枰饕樵儊磉^濾掉當(dāng)前事務(wù)不可見的任何對象版本堪夭。當(dāng)垃圾收集刪除任何事務(wù)不再可見的舊對象版本時(shí)愕把,相應(yīng)的索引條目也可以被刪除。在實(shí)踐中森爽,許多實(shí)現(xiàn)細(xì)節(jié)決定了多版本并發(fā)控制的性能恨豁。例如,如果同一對象的不同版本可以放入同一個(gè)頁面中爬迟,PostgreSQL就是采用這種優(yōu)化措施優(yōu)化避免更新索引橘蜜。
  • 在CouchDB,Datomic和LMDB中使用另一種方法付呕。雖然它們也使用B樹计福,但它們使用的是一種僅追加/寫時(shí)拷貝(append-only/copy-on-write) 的變體,它們在更新時(shí)不覆蓋樹的頁面凡涩,而為每個(gè)修改頁面創(chuàng)建一份副本棒搜。從父頁面直到樹根都會(huì)級(jí)聯(lián)更新,以指向它們子頁面的新版本活箕。任何不受寫入影響的頁面都不需要被復(fù)制力麸,并且保持不變。使用僅追加的B樹育韩,每個(gè)寫入事務(wù)(或一批事務(wù))都會(huì)創(chuàng)建一顆新的B樹克蚂,當(dāng)創(chuàng)建時(shí),從該特定樹根生長的樹就是數(shù)據(jù)庫的一個(gè)一致性快照筋讨。沒必要根據(jù)事務(wù)ID過濾掉對象埃叭,因?yàn)楹罄m(xù)寫入不能修改現(xiàn)有的B樹;它們只能創(chuàng)建新的樹根悉罕。但這種方法也需要一個(gè)負(fù)責(zé)壓縮和垃圾收集的后臺(tái)進(jìn)程赤屋。

3.2.5 mysql中的實(shí)現(xiàn)

image.png

InnoDB 為每個(gè)事務(wù)構(gòu)造了一個(gè)數(shù)組立镶,用來保存這個(gè)事務(wù)啟動(dòng)瞬間,當(dāng)前正在“活躍”的所有事務(wù) ID类早。


image.png

這樣媚媒,對于當(dāng)前事務(wù)的啟動(dòng)瞬間來說,一個(gè)數(shù)據(jù)版本的 row trx_id涩僻,有以下幾種可能:

如果落在綠色部分缭召,表示這個(gè)版本是已提交的事務(wù)或者是當(dāng)前事務(wù)自己生成的,這個(gè)數(shù)據(jù)是可見的逆日;
如果落在紅色部分嵌巷,表示這個(gè)版本是由將來啟動(dòng)的事務(wù)生成的,是肯定不可見的室抽;
如果落在黃色部分搪哪,那就包括兩種情況a.
若 row trx_id 在數(shù)組中,表示這個(gè)版本是由還沒提交的事務(wù)生成的坪圾,不可見噩死;b.
若 row trx_id 不在數(shù)組中,表示這個(gè)版本是已經(jīng)提交了的事務(wù)生成的神年,可見。

3.3 并發(fā)寫問題:更新丟失行嗤、幻讀已日、寫傾斜

  • 更新丟失: 兩個(gè)客戶端同時(shí)執(zhí)行讀取-修改-寫入序列。其中一個(gè)寫操作栅屏,在沒有合并另一個(gè)寫入變更情況下飘千,直接覆蓋了另一個(gè)寫操作的結(jié)果。所以導(dǎo)致數(shù)據(jù)丟失栈雳』つ危快照隔離的一些實(shí)現(xiàn)可以自動(dòng)防止這種異常,而另一些實(shí)現(xiàn)則需要手動(dòng)鎖定(SELECT FOR UPDATE)哥纫。

  • 寫傾斜: 事務(wù)首先查詢數(shù)據(jù)霉旗,根據(jù)它所看到的值作出決定,并將決定寫入數(shù)據(jù)庫蛀骇。但是厌秒,當(dāng)寫的時(shí)候,支持決定的前提已經(jīng)不再存在了擅憔。只有可序列化的隔離才能防止這種異常鸵闪。

  • 幻讀:事務(wù)讀取符合某些搜索條件的對象。另一個(gè)客戶端進(jìn)行寫入暑诸,影響搜索結(jié)果蚌讼”倩遥快照隔離可以防止簡單的幻像,但是寫傾斜環(huán)境中的幻讀需要特殊處理篡石,例如索引范圍鎖定芥喇。

3.3.1 防止更新丟失

  • 原子寫操作, 如: UPDATE counters SET value = value + 1 WHERE key = 'foo';
  • 顯式加鎖, 如果數(shù)據(jù)庫不支持內(nèi)置原子操作,防止丟失更新的另一個(gè)選擇是讓應(yīng)用程序顯式地鎖定將要更新的對象夏志。然后應(yīng)用程序可以執(zhí)行讀取-修改-寫入序列乃坤,如果任何其他事務(wù)嘗試同時(shí)讀取同一個(gè)對象,則強(qiáng)制等待沟蔑,直到第一個(gè)讀取-修改-寫入序列完成湿诊。
  • CAS
  • 之前提到的沖突解決

3.3.2 可串行化隔離

可串行化(Serializability)隔離通常被認(rèn)為是最強(qiáng)的隔離級(jí)別。它保證即使事務(wù)可以并行執(zhí)行瘦材,最終的結(jié)果也是一樣的厅须,就好像它們沒有任何并發(fā)性,連續(xù)挨個(gè)執(zhí)行一樣食棕。因此數(shù)據(jù)庫保證朗和,如果事務(wù)在單獨(dú)運(yùn)行時(shí)正常運(yùn)行,則它們在并發(fā)運(yùn)行時(shí)繼續(xù)保持正確 —— 換句話說簿晓,數(shù)據(jù)庫可以防止所有可能的競爭條件眶拉。

但如果可序列化隔離級(jí)別比弱隔離級(jí)別的要好得多,那為什么沒有被廣泛使用呢憔儿?為了回答這個(gè)問題忆植,我們需要看看可串行化究竟是什么,以及它們?nèi)绾螆?zhí)行谒臼。目前大多數(shù)提供可序列化的數(shù)據(jù)庫都使用了三種技術(shù)之一:

  • 字面意義上地串行執(zhí)行事務(wù)
  • 兩階段鎖定(2PL, two-phase locking)朝刊,幾十年來唯一可行的選擇。
  • 樂觀并發(fā)控制技術(shù)蜈缤,例如可序列化的快照隔離(serializable snapshot isolation)

1. 串行執(zhí)行事務(wù)

避免并發(fā)問題的最簡單方法就是完全不要并發(fā):在單個(gè)線程上按順序一次只執(zhí)行一個(gè)事務(wù)拾氓。這樣做就完全繞開了檢測/防止事務(wù)間沖突的問題,由此產(chǎn)生的隔離底哥,正是可序列化的定義咙鞍。

盡管這似乎是一個(gè)直白的想法,但數(shù)據(jù)庫設(shè)計(jì)人員在2007年左右才確定趾徽,單線程循環(huán)執(zhí)行事務(wù)是可行的奶陈。如果多線程并發(fā)在過去的30年中被認(rèn)為是獲得良好性能的關(guān)鍵所在,那么究竟是什么改變致使單線程執(zhí)行變?yōu)榭赡苣兀?/p>

兩個(gè)進(jìn)展引發(fā)了這個(gè)反思:

  • RAM足夠便宜了附较,許多場景現(xiàn)在都可以將完整的活躍數(shù)據(jù)集保存在內(nèi)存中吃粒。(當(dāng)事務(wù)需要訪問的所有數(shù)據(jù)都在內(nèi)存中時(shí),事務(wù)處理的執(zhí)行速度要比等待數(shù)據(jù)從磁盤加載時(shí)快得多拒课。
  • 數(shù)據(jù)庫設(shè)計(jì)人員意識(shí)到OLTP事務(wù)通常很短徐勃,而且只進(jìn)行少量的讀寫操作事示。相比之下,長時(shí)間運(yùn)行的分析查詢通常是只讀的僻肖,因此它們可以在串行執(zhí)行循環(huán)之外的一致快照(使用快照隔離)上運(yùn)行肖爵。

串行執(zhí)行事務(wù)的方法在VoltDB/H-Store,Redis和Datomic中實(shí)現(xiàn)臀脏。設(shè)計(jì)用于單線程執(zhí)行的系統(tǒng)有時(shí)可以比支持并發(fā)的系統(tǒng)更好劝堪,因?yàn)樗梢员苊怄i的協(xié)調(diào)開銷。但是其吞吐量僅限于單個(gè)CPU核的吞吐量揉稚。為了充分利用單一線程秒啦,需要與傳統(tǒng)形式不同的結(jié)構(gòu)的事務(wù)。

image.png

使用存儲(chǔ)過程+內(nèi)存存儲(chǔ)搀玖,使得在單個(gè)線程上執(zhí)行所有事務(wù)變得可行余境。由于不需要等待I/O,且避免了并發(fā)控制機(jī)制的開銷灌诅,它們可以在單個(gè)線程上實(shí)現(xiàn)相當(dāng)好的吞吐量芳来。

分區(qū)
順序執(zhí)行所有事務(wù)使并發(fā)控制簡單多了,但數(shù)據(jù)庫的事務(wù)吞吐量被限制為單機(jī)單核的速度猜拾。只讀事務(wù)可以使用快照隔離在其它地方執(zhí)行即舌,但對于寫入吞吐量較高的應(yīng)用,單線程事務(wù)處理器可能成為一個(gè)嚴(yán)重的瓶頸挎袜。

為了擴(kuò)展到多個(gè)CPU核心和多個(gè)節(jié)點(diǎn)侥涵,可以對數(shù)據(jù)進(jìn)行分區(qū),在VoltDB中支持這樣做宋雏。如果你可以找到一種對數(shù)據(jù)集進(jìn)行分區(qū)的方法,以便每個(gè)事務(wù)只需要在單個(gè)分區(qū)中讀寫數(shù)據(jù)务豺,那么每個(gè)分區(qū)就可以擁有自己獨(dú)立運(yùn)行的事務(wù)處理線程磨总。在這種情況下可以為每個(gè)分區(qū)指派一個(gè)獨(dú)立的CPU核,事務(wù)吞吐量就可以與CPU核數(shù)保持線性擴(kuò)展笼沥。

但是蚪燕,對于需要訪問多個(gè)分區(qū)的任何事務(wù),數(shù)據(jù)庫必須在相關(guān)的所有分區(qū)之間協(xié)調(diào)事務(wù)奔浅。存儲(chǔ)過程需要跨越所有分區(qū)鎖定執(zhí)行馆纳,以確保整個(gè)系統(tǒng)的可串行性。由于跨分區(qū)事務(wù)具有額外的協(xié)調(diào)開銷汹桦,所以它們比單分區(qū)事務(wù)慢得多鲁驶。 VoltDB報(bào)告的吞吐量大約是每秒1000個(gè)跨分區(qū)寫入,比單分區(qū)吞吐量低幾個(gè)數(shù)量級(jí)舞骆,并且不能通過增加更多的機(jī)器來增加钥弯。事務(wù)是否可以是劃分至單個(gè)分區(qū)很大程度上取決于應(yīng)用數(shù)據(jù)的結(jié)構(gòu)径荔。簡單的鍵值數(shù)據(jù)通常可以非常容易地進(jìn)行分區(qū)脆霎,但是具有多個(gè)二級(jí)索引的數(shù)據(jù)可能需要大量的跨分區(qū)協(xié)調(diào)总处,就不太合適了。

串行執(zhí)行小結(jié)

在特定約束條件下睛蛛,真的串行執(zhí)行事務(wù)鹦马,已經(jīng)成為一種實(shí)現(xiàn)可序列化隔離等級(jí)的可行辦法。

  • 每個(gè)事務(wù)都必須小而快忆肾,只要有一個(gè)緩慢的事務(wù)荸频,就會(huì)拖慢所有事務(wù)處理。
  • 僅限于活躍數(shù)據(jù)集可以全部加載進(jìn)內(nèi)存的情況难菌。很少訪問的數(shù)據(jù)可能會(huì)被移動(dòng)到磁盤试溯,但如果需要在單線程執(zhí)行的事務(wù)中訪問,系統(tǒng)就會(huì)變得非常慢郊酒。
  • 寫入吞吐量必須低到能在單個(gè)CPU核上處理遇绞,如若不然,事務(wù)需要能劃分至單個(gè)分區(qū)燎窘,且不需要跨分區(qū)協(xié)調(diào)摹闽。
  • 跨分區(qū)事務(wù)是可能的,但是占比必須很小褐健。

2. 兩階段加鎖

什么是兩階段加鎖付鹿?

之前我們看到鎖通常用于防止臟寫(參閱“沒有臟寫”一節(jié)):如果兩個(gè)事務(wù)同時(shí)嘗試寫入同一個(gè)對象,則鎖可確保第二個(gè)寫入必須等到第一個(gè)寫入完成事務(wù)(中止或提交)蚜迅,然后才能繼續(xù)舵匾。

兩階段鎖定定類似,但使鎖的要求更強(qiáng)谁不。只要沒有寫入坐梯,就允許多個(gè)事務(wù)同時(shí)讀取同一個(gè)對象。但對象只要有寫入(修改或刪除)刹帕,就需要獨(dú)占訪問(exclusive access) 權(quán)限:

  • 如果事務(wù)A讀取了一個(gè)對象吵血,并且事務(wù)B想要寫入該對象,那么B必須等到A提交或中止才能繼續(xù)偷溺。 (這確保B不能在A底下意外地改變對象蹋辅。)
  • 如果事務(wù)A寫入了一個(gè)對象,并且事務(wù)B想要讀取該對象挫掏,則B必須等到A提交或中止才能繼續(xù)侦另。 (像圖7-1那樣讀取舊版本的對象在2PL下是不可接受的。)

2PL不僅在并發(fā)寫操作之前互斥,讀取也會(huì)和修改互斥淋肾×蚵椋快照級(jí)別隔離的口號(hào)是”讀寫互不干擾“,這是2PL和快照隔離之間的關(guān)鍵區(qū)別樊卓。另一方面拿愧,因?yàn)?PL提供了可序列化的性質(zhì),它可以防止早先討論的所有競爭條件碌尔,包括丟失更新和寫傾斜浇辜。

如何實(shí)現(xiàn)兩階段加鎖?

2PL用于MySQL(InnoDB)和SQL Server中的可序列化隔離級(jí)別唾戚,以及DB2中的可重復(fù)讀隔離柳洋。

讀與寫的阻塞是通過為數(shù)據(jù)庫中每個(gè)對象添加鎖來實(shí)現(xiàn)的。鎖可以處于共享模式(shared mode)獨(dú)占模式(exclusive mode)叹坦。鎖使用如下:

  • 若事務(wù)要讀取對象熊镣,則須先以共享模式獲取鎖。允許多個(gè)事務(wù)同時(shí)持有共享鎖募书。但如果另一個(gè)事務(wù)已經(jīng)在對象上持有排它鎖绪囱,則這些事務(wù)必須等待。
  • 若事務(wù)要寫入一個(gè)對象莹捡,它必須首先以獨(dú)占模式獲取該鎖鬼吵。沒有其他事務(wù)可以同時(shí)持有鎖(無論是共享模式還是獨(dú)占模式),所以如果對象上存在任何鎖篮赢,該事務(wù)必須等待齿椅。
  • 如果事務(wù)先讀取再寫入對象,則它可能會(huì)將其共享鎖升級(jí)為獨(dú)占鎖启泣。升級(jí)鎖的工作與直接獲得排他鎖相同涣脚。
  • 事務(wù)獲得鎖之后,必須繼續(xù)持有鎖直到事務(wù)結(jié)束(提交或中止)寥茫。這就是“兩階段”這個(gè)名字的來源:第一階段(當(dāng)事務(wù)正在執(zhí)行時(shí))獲取鎖遣蚀,第二階段(在事務(wù)結(jié)束時(shí))釋放所有的鎖

由于使用了這么多的鎖坠敷,因此很可能會(huì)發(fā)生:事務(wù)A等待事務(wù)B釋放它的鎖,反之亦然射富。這種情況叫做死鎖(Deadlock)膝迎。數(shù)據(jù)庫會(huì)自動(dòng)檢測事務(wù)之間的死鎖,并中止其中一個(gè)胰耗,以便另一個(gè)繼續(xù)執(zhí)行限次。被中止的事務(wù)需要由應(yīng)用程序重試。

兩階段鎖定的性能

兩階段鎖定的巨大缺點(diǎn),以及70年代以來沒有被所有人使用的原因卖漫,是其性能問題费尽。兩階段鎖定下的事務(wù)吞吐量與查詢響應(yīng)時(shí)間要比弱隔離級(jí)別下要差得多。

這一部分是由于獲取和釋放所有這些鎖的開銷羊始,但更重要的是由于并發(fā)性的降低旱幼。按照設(shè)計(jì),如果兩個(gè)并發(fā)事務(wù)試圖做任何可能導(dǎo)致競爭條件的事情突委,那么必須等待另一個(gè)完成柏卤。

傳統(tǒng)的關(guān)系數(shù)據(jù)庫不限制事務(wù)的持續(xù)時(shí)間,因?yàn)樗鼈兪菫榈却祟愝斎氲慕换ナ綉?yīng)用而設(shè)計(jì)的匀油。因此缘缚,當(dāng)一個(gè)事務(wù)需要等待另一個(gè)事務(wù)時(shí),等待的時(shí)長并沒有限制敌蚜。即使你保證所有的事務(wù)都很短桥滨,如果有多個(gè)事務(wù)想要訪問同一個(gè)對象,那么可能會(huì)形成一個(gè)隊(duì)列弛车,所以事務(wù)可能需要等待幾個(gè)其他事務(wù)才能完成齐媒。

因此,運(yùn)行2PL的數(shù)據(jù)庫可能具有相當(dāng)不穩(wěn)定的延遲帅韧,如果在工作負(fù)載中存在爭用里初,那么可能高百分位點(diǎn)處的響應(yīng)會(huì)非常的慢『鲋郏可能只需要一個(gè)緩慢的事務(wù)双妨,或者一個(gè)訪問大量數(shù)據(jù)并獲取許多鎖的事務(wù),就能把系統(tǒng)的其他部分拖慢叮阅,甚至迫使系統(tǒng)停機(jī)刁品。當(dāng)需要穩(wěn)健的操作時(shí),這種不穩(wěn)定性是有問題的浩姥。

基于鎖實(shí)現(xiàn)的讀已提交隔離級(jí)別可能發(fā)生死鎖挑随,但在基于2PL實(shí)現(xiàn)的可序列化隔離級(jí)別中,它們會(huì)出現(xiàn)的頻繁的多(取決于事務(wù)的訪問模式)勒叠。這可能是一個(gè)額外的性能問題:當(dāng)事務(wù)由于死鎖而被中止并被重試時(shí)兜挨,它需要從頭重做它的工作。如果死鎖很頻繁眯分,這可能意味著巨大的浪費(fèi)拌汇。

3. 謂詞鎖和索引區(qū)間鎖

謂詞鎖

從概念上講,我們需要一個(gè)謂詞鎖(predicate lock)弊决。它類似于前面描述的共享/排它鎖噪舀,但不屬于特定的對象(例如魁淳,表中的一行),它屬于所有符合某些搜索條件的對象与倡。
謂詞鎖限制訪問界逛,如下所示:

  1. 如果事務(wù)A想要讀取匹配某些條件的對象,就像在這個(gè) SELECT 查詢中那樣纺座,它必須獲取查詢條件上的共享謂詞鎖(shared-mode predicate lock)息拜。如果另一個(gè)事務(wù)B持有任何滿足這一查詢條件對象的排它鎖,那么A必須等到B釋放它的鎖之后才允許進(jìn)行查詢比驻。
  2. 如果事務(wù)A想要插入该溯,更新或刪除任何對象,則必須首先檢查舊值或新值是否與任何現(xiàn)有的謂詞鎖匹配别惦。如果事務(wù)B持有匹配的謂詞鎖狈茉,那么A必須等到B已經(jīng)提交或中止后才能繼續(xù)。

這里的關(guān)鍵思想是掸掸,謂詞鎖甚至適用于數(shù)據(jù)庫中尚不存在氯庆,但將來可能會(huì)添加的對象(幻象)。如果使用兩階段鎖定+謂詞鎖扰付,則數(shù)據(jù)庫將阻止所有形式的寫傾斜和其他競爭條件堤撵,因此其隔離實(shí)現(xiàn)了可串行化。

區(qū)間索引鎖
mysql使用Next-Key Lock來實(shí)現(xiàn)對謂詞鎖的近似羽莺。

3.4 Mysql中的鎖

https://i6448038.github.io/2019/02/23/mysql-lock/

全局讀鎖

MySQL 提供了一個(gè)加全局讀鎖的方法实昨,命令是 Flush tables with read lock (FTWRL)。全局鎖的典型使用場景是盐固,做全庫邏輯備份荒给。

表級(jí)鎖

MySQL 里面表級(jí)別的鎖有兩種:一種是表鎖,一種是元數(shù)據(jù)鎖(meta data lock刁卜,MDL)志电。

表鎖的語法是 lock tables … read/write。與 FTWRL 類似蛔趴,可以用 unlock tables 主動(dòng)釋放鎖挑辆,也可以在客戶端斷開的時(shí)候自動(dòng)釋放。需要注意孝情,lock tables 語法除了會(huì)限制別的線程的讀寫外鱼蝉,也限定了本線程接下來的操作對象。

在還沒有出現(xiàn)更細(xì)粒度的鎖的時(shí)候箫荡,表鎖是最常用的處理并發(fā)的方式魁亦。而對于 InnoDB 這種支持行鎖的引擎,一般不使用 lock tables 命令來控制并發(fā)菲茬,畢竟鎖住整個(gè)表的影響面還是太大吉挣。

另一類表級(jí)的鎖是 MDL(metadata lock)。MDL 不需要顯式使用婉弹,在訪問一個(gè)表的時(shí)候會(huì)被自動(dòng)加上睬魂。MDL 的作用是,保證讀寫的正確性镀赌。你可以想象一下氯哮,如果一個(gè)查詢正在遍歷一個(gè)表中的數(shù)據(jù),而執(zhí)行期間另一個(gè)線程對這個(gè)表結(jié)構(gòu)做變更商佛,刪了一列喉钢,那么查詢線程拿到的結(jié)果跟表結(jié)構(gòu)對不上,肯定是不行的良姆。因此肠虽,在 MySQL 5.5 版本中引入了 MDL,當(dāng)對一個(gè)表做增刪改查操作的時(shí)候玛追,加 MDL 讀鎖税课;當(dāng)要對表做結(jié)構(gòu)變更操作的時(shí)候,加 MDL 寫鎖痊剖。

MDL用于防止DDL和DML并發(fā)的沖突

意向鎖

InnoDB支持多粒度的鎖韩玩,即:允許表鎖和行鎖同時(shí)存在。
但是陆馁,假如表鎖覆蓋了行鎖的數(shù)據(jù)找颓,所以表鎖和行鎖也會(huì)產(chǎn)生沖突。如何判斷表鎖與行鎖的沖突性叮贩?

加行鎖之前加意向鎖击狮,意向鎖即為表級(jí)別的鎖,意思就是:這種表中有幾行數(shù)據(jù)被加鎖妇汗,意向鎖不會(huì)阻塞除全表掃描以外的任何請求

IX帘不,IS是表級(jí)鎖,不會(huì)和行級(jí)的X杨箭,S鎖發(fā)生沖突寞焙。只會(huì)和表級(jí)的X,S發(fā)生沖突互婿。

也許有人會(huì)問:“意向鎖存在的意義是什么呢捣郊?沒有意向鎖,行鎖和表鎖照樣可以共存按炔巍呛牲?”
試問如何共存?
“查看表中某一行存在X鎖”
“如何查看呢驮配?”
唯有全表掃描…
意向鎖的存在就是解決了“全表掃描”的性能問題娘扩,所以着茸,意向鎖一定是“表級(jí)”鎖,告訴整張表XXX行存在X鎖琐旁。此時(shí)假如進(jìn)行表操作就會(huì)被阻塞涮阔。

行鎖

《Mysql實(shí)戰(zhàn)45講》的第20章和第21章把這個(gè)問題講得很明白.

行鎖的實(shí)現(xiàn): https://lanjingling.github.io/2015/10/10/mysql-hangsuo/

InnoDB行鎖是通過給索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,這一點(diǎn)MySQL與Oracle不同灰殴,后者是通過在數(shù)據(jù)塊中對相應(yīng)數(shù)據(jù)行加鎖來實(shí)現(xiàn)的敬特。 InnoDB這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:只有通過索引條件檢索數(shù)據(jù),InnoDB才使用行級(jí)鎖牺陶,否則伟阔,InnoDB將使用表鎖!

  • 原則 1:加鎖的基本單位是 next-key lock掰伸。希望你還記得皱炉,next-key lock 是前開后閉區(qū)間。
  • 原則 2:查找過程中訪問到的對象才會(huì)加鎖狮鸭。
  • 優(yōu)化 1:索引上的等值查詢娃承,給唯一索引加鎖的時(shí)候,next-key lock 退化為行鎖怕篷。
  • 優(yōu)化 2:索引上的等值查詢历筝,向右遍歷時(shí)且最后一個(gè)值不滿足等值條件的時(shí)候,next-key lock退化為間隙鎖廊谓。
  • 一個(gè) bug:唯一索引上的范圍查詢會(huì)訪問到不滿足條件的第一個(gè)值為止梳猪。

詳細(xì)加鎖按理可以參見: https://time.geekbang.org/column/article/75659

在可重復(fù)讀隔離級(jí)別下, 采用Next-Key Locking的方式來加鎖, 而在讀已提交隔離級(jí)別下, 僅采用Record Lock

跟間隙鎖存在沖突關(guān)系的,是“往這個(gè)間隙中插入一個(gè)記錄”這個(gè)操作蒸痹。間隙鎖之間都不存在沖突關(guān)系春弥。

間隙鎖和 next-key lock 的引入,幫我們解決了幻讀的問題叠荠,但同時(shí)也帶來了一些“困擾”匿沛。



CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
image.png

image.png

image.png


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榛鼎,隨后出現(xiàn)的幾起案子逃呼,更是在濱河造成了極大的恐慌,老刑警劉巖者娱,帶你破解...
    沈念sama閱讀 212,294評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抡笼,死亡現(xiàn)場離奇詭異,居然都是意外死亡黄鳍,警方通過查閱死者的電腦和手機(jī)推姻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來框沟,“玉大人藏古,你說我怎么就攤上這事增炭。” “怎么了拧晕?”我有些...
    開封第一講書人閱讀 157,790評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵弟跑,是天一觀的道長。 經(jīng)常有香客問我防症,道長,這世上最難降的妖魔是什么哎甲? 我笑而不...
    開封第一講書人閱讀 56,595評(píng)論 1 284
  • 正文 為了忘掉前任蔫敲,我火速辦了婚禮,結(jié)果婚禮上炭玫,老公的妹妹穿的比我還像新娘奈嘿。我一直安慰自己,他們只是感情好吞加,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評(píng)論 6 386
  • 文/花漫 我一把揭開白布裙犹。 她就那樣靜靜地躺著,像睡著了一般衔憨。 火紅的嫁衣襯著肌膚如雪叶圃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,906評(píng)論 1 290
  • 那天践图,我揣著相機(jī)與錄音掺冠,去河邊找鬼。 笑死码党,一個(gè)胖子當(dāng)著我的面吹牛德崭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播揖盘,決...
    沈念sama閱讀 39,053評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼眉厨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兽狭?” 一聲冷哼從身側(cè)響起憾股,我...
    開封第一講書人閱讀 37,797評(píng)論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箕慧,沒想到半個(gè)月后荔燎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,250評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡销钝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評(píng)論 2 327
  • 正文 我和宋清朗相戀三年有咨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒸健。...
    茶點(diǎn)故事閱讀 38,711評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡座享,死狀恐怖婉商,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渣叛,我是刑警寧澤丈秩,帶...
    沈念sama閱讀 34,388評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站淳衙,受9級(jí)特大地震影響蘑秽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜箫攀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評(píng)論 3 316
  • 文/蒙蒙 一肠牲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧靴跛,春花似錦缀雳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绝葡,卻和暖如春深碱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藏畅。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評(píng)論 1 266
  • 我被黑心中介騙來泰國打工莹痢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墓赴。 一個(gè)月前我還...
    沈念sama閱讀 46,461評(píng)論 2 360
  • 正文 我出身青樓竞膳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诫硕。 傳聞我的和親對象是個(gè)殘疾皇子坦辟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評(píng)論 2 350