毫無(wú)疑問(wèn),并發(fā)控制方向的內(nèi)容是我們學(xué)習(xí)的重點(diǎn)和難點(diǎn),在一段時(shí)間的學(xué)習(xí)之后,通常會(huì)有一些挫敗感,這是一種似懂非懂的感覺(jué),主要的原因其實(shí)細(xì)究起來(lái)理解為:使用并發(fā)時(shí)需要解決的問(wèn)題有多個(gè),而要實(shí)現(xiàn)并發(fā)的方案有多種,它們兩者之間沒(méi)有明顯的映射關(guān)系,如下圖所示。

接下來(lái)我們來(lái)聊一下對(duì)于并發(fā)控制的理解,首先需要明確一個(gè)問(wèn)題,那就是為什么需要事務(wù)。
- 為什么需要事務(wù)
為什么需要事務(wù),聽(tīng)起來(lái)是個(gè)多余的問(wèn)題,究其原因,事務(wù)處理機(jī)制,要保證用戶(hù)的數(shù)據(jù)操作對(duì)數(shù)據(jù)是“安全”的,比如我們要守護(hù)的銀行卡余額,我們希望對(duì)它的操作是穩(wěn)定準(zhǔn)確,而且絕對(duì)是安全的。
那么什么樣的操作才是安全的呢,這就引出了事務(wù)的ACID特性,ACID的解釋和說(shuō)明如下所示。
ACID特性
解釋
原子性(atomicity)
一個(gè)事務(wù)要么全部執(zhí)行,要么完全不執(zhí)行
一致性(consistency)
事務(wù)在開(kāi)始和結(jié)束時(shí),應(yīng)該始終滿足一致性約束
隔離性(isolation)
在事務(wù)操作時(shí),其他事務(wù)的操作不能影響到當(dāng)前的事務(wù)操作
持久性(durability)
事務(wù)操作的結(jié)果是具有持久性的
這個(gè)理解起來(lái)就相對(duì)簡(jiǎn)單了,比如我去ATM機(jī)取款,要么成功,要么提示余額不足(原子性),比如我取了1000元,那么從ATM里面取出的也應(yīng)該是1000元,不多不少(一致性),我取款的時(shí)候有人給我轉(zhuǎn)賬,我不應(yīng)該拒絕這樣的操作(隔離性),取款完畢,我們可以打一張回執(zhí)單,上面會(huì)有我們的余額(持久化),之后查多少次都不會(huì)變。
順著這個(gè)思路來(lái)看,我們把查詢(xún)余額看做是讀操作,存錢(qián),取款看做是寫(xiě)操作,很多讀寫(xiě)操作的并發(fā)都相對(duì)容易理解了。
對(duì)于這樣的操作我們分為讀和寫(xiě),它有如下兩種組合:
(1)讀-讀操作
(2)讀-寫(xiě)操作
其中我們經(jīng)常聽(tīng)到的臟讀,不可重復(fù)讀,幻讀都是在讀-寫(xiě)操作中出現(xiàn)的概念,我們可以用下面的三句話來(lái)概括:
l 寫(xiě)在前,讀在后:臟讀
l 讀在前,寫(xiě)在后:不可重復(fù)讀
l 讀在前,寫(xiě)在后,然后又讀:幻讀
我們可以假設(shè)生活中的幾個(gè)場(chǎng)景,來(lái)吃透這三種不是很容易理解的概念,我們就以購(gòu)物車(chē)為例吧,故事的背景是一對(duì)情侶,某天早上女生上班前對(duì)男生說(shuō),幫我關(guān)一下電腦,男生關(guān)電腦時(shí)發(fā)現(xiàn)桌面首頁(yè)顯示女生的賬號(hào)登錄了一個(gè)購(gòu)物網(wǎng)站,購(gòu)物車(chē)?yán)镉幸粋€(gè)化妝品套裝,但是還沒(méi)有下單如下圖所示:

l 1)男生說(shuō)原來(lái)不是關(guān)電腦這么簡(jiǎn)單啊,于是就默默下單提交了,這種情況就是臟讀,事務(wù)B讀到了事務(wù)A未提交的數(shù)據(jù)狀態(tài)。
l 2)男生想多大點(diǎn)事,一套不夠,再買(mǎi)一套,于是點(diǎn)擊添加了一套,結(jié)果女生下班后,帶著期待的心情打開(kāi)購(gòu)物車(chē),發(fā)現(xiàn)化妝品沒(méi)變,但是數(shù)量是2套,這就是不可重復(fù)讀,重點(diǎn)在于修改,一個(gè)事務(wù)前后兩次讀取的結(jié)果值并不一致,導(dǎo)致了不可重復(fù)讀,面對(duì)的是相同的查詢(xún)數(shù)據(jù),類(lèi)似 product_code=’化妝品套裝’
l 3)男生明白了,查看了女生瀏覽的其他幾款化妝品,把它們都加入了購(gòu)物車(chē),結(jié)果女生下班后,查看購(gòu)物車(chē),發(fā)現(xiàn)除了之前的那款化妝品,一下子又多了好幾款其他的化妝品套裝,明明只中意了一款啊,這種情況就是幻讀,幻讀面對(duì)的是一類(lèi)數(shù)據(jù),在這里就是以購(gòu)物車(chē)?yán)锏乃猩唐纷鳛閰⒖肌?/p>
我們簡(jiǎn)單總結(jié)下,不可重復(fù)讀和幻讀有些類(lèi)似:一個(gè)事務(wù)多次讀取某條數(shù)據(jù),發(fā)現(xiàn)讀取的數(shù)據(jù)不完全相同 ,
兩者的不同點(diǎn)在于,不可重復(fù)讀針對(duì)數(shù)據(jù)的修改造成的讀不一致,而幻讀針對(duì)數(shù)據(jù)的插入和刪除造成的讀不一致,如同發(fā)生幻覺(jué)一樣。
- MySQL并發(fā)控制技術(shù)方案
數(shù)據(jù)庫(kù)的一個(gè)核心方向就是并發(fā)控制了,并發(fā)是對(duì)臨界資源進(jìn)行操作,通過(guò)并發(fā)控制技術(shù)來(lái)確保整個(gè)過(guò)程中對(duì)于數(shù)據(jù)的操作是“安全”的。
總體來(lái)說(shuō),有以下的兩類(lèi)并發(fā)控制技術(shù):鎖機(jī)制 (Locking)和多版本并發(fā)控制(MVCC)
(1)鎖機(jī)制 (Locking)
通過(guò)鎖機(jī)制可以保證數(shù)據(jù)一致性,整體的場(chǎng)景感覺(jué)無(wú)非是讀-讀,讀-寫(xiě),寫(xiě)-寫(xiě)這幾類(lèi)并發(fā),看起來(lái)容易,但是融合到業(yè)務(wù)場(chǎng)景中是千差萬(wàn)別,相對(duì)是比較復(fù)雜的。
(2)多版本并發(fā)控制(MVCC)
MVCC(Multiversion Concurrency Control)是側(cè)重于讀寫(xiě)并發(fā)的改善機(jī)制,它可以避免寫(xiě)操作堵塞讀操作的并發(fā)問(wèn)題,通過(guò)使用數(shù)據(jù)的多個(gè)版本保證并發(fā)讀寫(xiě)不沖突的一種機(jī)制,它只是一種標(biāo)準(zhǔn),并不是規(guī)定了明細(xì)的實(shí)現(xiàn)細(xì)節(jié),所以在數(shù)據(jù)庫(kù)方向上大體會(huì)有一些MVCC的不同實(shí)現(xiàn)。
寫(xiě)-寫(xiě)的場(chǎng)景其實(shí)相對(duì)容易理解,為了保證在同一時(shí)間完成數(shù)據(jù)的一致性操作,我們需要通過(guò)鎖的方式來(lái)控制,為了方便理解,整個(gè)過(guò)程簡(jiǎn)單理解是串行的,有一些改進(jìn)的細(xì)節(jié)我們?cè)诤竺鏁?huì)說(shuō)。
這里要先引出一個(gè)概念,就是2PL(Two-Phase Locking ,二階段鎖),這個(gè)過(guò)程我們舉個(gè)例子就很容易理解了。加鎖階段只加鎖,解鎖階段只放鎖,就好像我們呼吸一樣,吸氣,呼氣,一張一弛,但是不會(huì)彼此交叉。把這個(gè)過(guò)程細(xì)化到一個(gè)數(shù)據(jù)并發(fā)中的場(chǎng)景:
(1) 操作數(shù)據(jù)前,加鎖,互相排斥,不允許其他并發(fā)任務(wù)操作。
(2) 操作數(shù)據(jù)后,解鎖,其他任務(wù)可以繼續(xù)執(zhí)行。
這種鎖定的方式相對(duì)比較單一而且粒度太粗,這樣會(huì)導(dǎo)致在并發(fā)讀任務(wù)都會(huì)阻塞,對(duì)于并發(fā)的性能影響是很大的,所以InnoDB實(shí)現(xiàn)了兩種類(lèi)型的行鎖。
l 共享鎖(S):允許一個(gè)事務(wù)去讀一行,阻止其他事務(wù)獲得相同的數(shù)據(jù)集的排他鎖。
l 排他鎖(X):允許獲得排他鎖的事務(wù)更新數(shù)據(jù),但是組織其他事務(wù)獲得相同數(shù)據(jù)集的共享鎖和排他鎖。
簡(jiǎn)單小結(jié)為:
l 共享鎖(S)之間不互斥,讀讀操作可以并行。
l 排它鎖(X)是互斥關(guān)系,讀寫(xiě),寫(xiě)寫(xiě)操作不可以并行。
一些常見(jiàn)的共享鎖的使用方式有:
共享鎖:
select * from table_name where .....lock in share mode
排他鎖:
select * from table_name where .....for update
通過(guò)這一層的改進(jìn),可以對(duì)于讀讀并發(fā)的場(chǎng)景有了較好的支撐,但是寫(xiě)入的過(guò)程中,讀任務(wù)還是會(huì)被阻塞,對(duì)于讀寫(xiě)的操作還是存在瓶頸,所以在這個(gè)層面上引入了MVCC,在詳細(xì)展開(kāi)之前,我們需要了解下MVCC并發(fā)控制中的兩類(lèi)讀操作,快照讀(Snapshot Read)和當(dāng)前讀(Current Read),其中快照讀讀取的是數(shù)據(jù)的可見(jiàn)版本,可能是數(shù)據(jù)的歷史鏡像,這個(gè)過(guò)程是不加鎖的,而當(dāng)前讀讀取的是最新的版本,會(huì)加上鎖,保證其他事務(wù)不會(huì)再修改這條記錄。
比如我們觸發(fā)了一條select操作: select * from test where id=100; id為主鍵,這條語(yǔ)句對(duì)應(yīng)的操作就是快照讀,而我們上面剛剛列舉的共享鎖和排它鎖的SQL還有常見(jiàn)的DML都屬于當(dāng)前讀,操作過(guò)程中會(huì)讀取當(dāng)前最新的版本,保證其他事務(wù)不能修改當(dāng)前記錄。
我們通過(guò)思維導(dǎo)圖的形式簡(jiǎn)單對(duì)并發(fā)控制技術(shù)做個(gè)總結(jié),如下圖所示。
