解鎖MySQL的黑科技:事務(wù)與隔離
1. 引言
大家好,我是小?,一個(gè)漂泊江湖多年的 985 非科班程序員,曾混跡于國(guó)企、互聯(lián)網(wǎng)大廠和創(chuàng)業(yè)公司的后臺(tái)開(kāi)發(fā)攻城獅。
最近小?在梳理我之前的面試資料時(shí)發(fā)現(xiàn),面試過(guò)程中,基本上都會(huì)問(wèn)到 MySQL 數(shù)據(jù)庫(kù)相關(guān)的知識(shí)點(diǎn)。
而 MySQL 中,問(wèn)得最多的就是事務(wù)、隔離級(jí)別以及 MVCC 這幾個(gè),無(wú)論是互聯(lián)網(wǎng)大廠、小廠,甚至是國(guó)企,它們的覆蓋率竟高達(dá) 80%。
其實(shí)面試官也知道,八股文誰(shuí)都會(huì)背,但是可以說(shuō)明白,甚至說(shuō)透徹的候選人卻是鳳毛麟角。
所以今天小?就帶大家來(lái)解鎖那些藏在 MySQL 底層的黑科技:事務(wù)與隔離。
2、事務(wù)
2.1 直播打賞
首先,讓我們來(lái)談?wù)勈聞?wù)。
事務(wù)就像一場(chǎng)魔法表演,它可以確保一系列數(shù)據(jù)庫(kù)操作要么全部執(zhí)行成功,要么一點(diǎn)都不執(zhí)行。
假設(shè)你在看直播時(shí),想打賞 500 塊給美女主播,這時(shí)需要扣除你的賬戶余額,并同時(shí)增加美女主播的賬戶金額。
如果轉(zhuǎn)賬的兩個(gè)操作中的一個(gè)失敗,那你就可能損失金錢(qián)或者讓金錢(qián)消失不見(jiàn),美女主播也就收不到錢(qián)了。
這時(shí),事務(wù)就派上用場(chǎng)了。
它可以保證這兩個(gè)操作要么同時(shí)成功,要么同時(shí)失敗,絕不會(huì)出現(xiàn)一半成功一半失敗的尷尬局面。
所以,我們總結(jié)一下:
Q:數(shù)據(jù)庫(kù)為什么要有事務(wù)?
A:為了保證業(yè)務(wù)正常運(yùn)轉(zhuǎn),數(shù)據(jù)最終一致。
2.2 事務(wù)特性
明白了什么是事務(wù),以及為什么需要事務(wù)。
接下來(lái)我們聊一聊事務(wù)的 4 個(gè)特性:原子性、一致性、隔離性和持久性,簡(jiǎn)稱 ACID。
原子性(Atomicity)
原子性是指事務(wù)包含的操作要么全部成功,要么全部不成功。
比如 A、B 賬戶的初始余額為 800 元,100元。此時(shí),A 向 B 轉(zhuǎn)賬 500 元,那么分解開(kāi)來(lái)就是 A 賬戶減 500 元,B 賬戶加 500 元。
最終結(jié)果是 A 賬戶余額為 300 元,B 賬戶余額為 600 元。這兩個(gè)賬戶余額更新的操作,要么全部執(zhí)行,要么都不執(zhí)行。
拿給美女主播打賞的例子,原子性可以保證:要么錢(qián)還在,要么錢(qián)轉(zhuǎn)到主播賬戶上并收獲主播的一聲謝謝哥哥!
一致性(Consistency)
事務(wù)執(zhí)行前,和執(zhí)行后都會(huì)保持一致性狀態(tài)。
A、B 賬戶在轉(zhuǎn)賬后,會(huì)發(fā)生兩種情況:
- 錢(qián)轉(zhuǎn)到 B 賬戶里了,這時(shí) A、B 賬戶分別為 300、600 元;
- 錢(qián)轉(zhuǎn)出去的過(guò)程中數(shù)據(jù)庫(kù)網(wǎng)絡(luò)斷開(kāi),事務(wù)回滾了,A、B 賬戶還是 800、100 元。
無(wú)論怎樣,事務(wù)發(fā)生前后,A、B 銀行賬戶的總額都應(yīng)該為 900 元,這就是前后一致性。
隔離性(Isolation)
隔離性是當(dāng)多個(gè)用戶并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),不管是不是操作同一個(gè)庫(kù)、還是同一張表,數(shù)據(jù)庫(kù)為每一個(gè)用戶開(kāi)啟的事務(wù),不能被其他事務(wù)的操作所干擾,多個(gè)并發(fā)事務(wù)之間也要相互隔離。
比如,A 向 B 轉(zhuǎn)賬的時(shí)候,不管別人怎么轉(zhuǎn)賬,都不會(huì)影響他們的交易。
圖片
拿給美女主播打賞的例子,隔離性就是:不管有多少人在給主播打賞,都不會(huì)影響你轉(zhuǎn)錢(qián)的事務(wù),也就不會(huì)影響主播叫你一聲好哥哥!
持久性(Durability)
一個(gè)事務(wù)一旦被提交了,那么對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)的改變就是持久性的【即保存到磁盤(pán)里】,即便是在數(shù)據(jù)庫(kù)系統(tǒng)遇到故障的情況下也不會(huì)丟失提交事務(wù)的操作。
拿給美女主播打賞的例子,持久性就是:你只要給主播轉(zhuǎn)了錢(qián),錢(qián)就進(jìn)了她的賬戶,無(wú)論收獲主播的多少聲謝謝好哥哥,錢(qián)也回不來(lái)了。
接下來(lái),我們總結(jié)一下:
- Q:為什么事務(wù)有這幾大特性?
- A:我們要保證事務(wù)的數(shù)據(jù)一致性,就需要一些手段來(lái)實(shí)現(xiàn),這幾種手段就是事務(wù)的幾個(gè)特性。
它們分別是原子性、一致性、隔離性和持久性,其中一致性是目的,而原子性、一致性和隔離性都是為了實(shí)現(xiàn)數(shù)據(jù)一致性的手段。
3. 事務(wù)并發(fā)和隔離
事務(wù)并發(fā)
并發(fā)是指計(jì)算機(jī)系統(tǒng)或程序在同一時(shí)間內(nèi)同時(shí)處理多個(gè)任務(wù)或操作的能力,也就是允許多個(gè)用戶進(jìn)程去處理同一塊臨界區(qū)。
想從進(jìn)程或處理器的角度來(lái)理解并發(fā)的,可以看我之前的這篇文章:GPM調(diào)度模型
拿打賞主播來(lái)舉例,并發(fā)就是多個(gè)觀眾都想打賞主播,如果你們一起轉(zhuǎn)錢(qián),那主播的賬戶余額該怎么修改呢?
這里的任務(wù)就是轉(zhuǎn)賬,用戶進(jìn)程就是負(fù)責(zé)交易的服務(wù)器進(jìn)程,臨界區(qū)就是主播賬戶的存儲(chǔ)空間。
如果出現(xiàn)了事務(wù)并發(fā),就會(huì)帶來(lái)一些意想不到的問(wèn)題,例如常見(jiàn)的臟寫(xiě)、臟讀、重復(fù)讀和幻讀。
臟寫(xiě)
臟寫(xiě)是指:在事務(wù)并發(fā)的時(shí)候,一個(gè)事務(wù)可以修改另外一個(gè)正在進(jìn)行中的事務(wù)的數(shù)據(jù),這可能會(huì)導(dǎo)致一個(gè)寫(xiě)的事務(wù)會(huì)覆蓋另外一個(gè)寫(xiě)的事務(wù)數(shù)據(jù)。
當(dāng)你和小帥一起給美女主播打賞時(shí),你打賞了 500 塊,小帥打賞了 1000 塊,在寫(xiě)入數(shù)據(jù)庫(kù)的時(shí)候,你寫(xiě)入的數(shù)據(jù)被小帥的數(shù)據(jù)給覆蓋了。
最后導(dǎo)致的結(jié)果就是,你錢(qián)沒(méi)了,而美女主播在直播間說(shuō)的是謝謝小帥哥哥的打賞!
事務(wù)隔離
500 塊錢(qián)沒(méi)了,美女主播還不理你,你很傷心,但是不知道怎么辦?
別難過(guò)!事務(wù)隔離可以幫你。
MySQL 提供了事務(wù)隔離級(jí)別,包括:讀未提交、讀已提交、可重復(fù)讀以及串行化,來(lái)解決事務(wù)中各種并發(fā)問(wèn)題,專治各種不開(kāi)心。
RU - 讀未提交(Read uncommitted)
RU(讀未提交)是指,如果一個(gè)事務(wù)開(kāi)始寫(xiě)數(shù)據(jù),則另外一個(gè)事務(wù)不允許同時(shí)進(jìn)行寫(xiě)操作,但允許其他事務(wù)讀取此行數(shù)據(jù)。
RU 可以排他寫(xiě),但是不排斥讀線程實(shí)現(xiàn)。
這種隔離級(jí)別解決了上面的臟寫(xiě)問(wèn)題,但可能會(huì)出現(xiàn)臟讀,即事務(wù) B 讀取到了事務(wù) A 未提交的數(shù)據(jù)。
你想給美女主播打賞 500 塊,發(fā)現(xiàn)銀行卡余額只有 300 塊,這時(shí)你想到了前幾天找你借了 500 塊錢(qián)的小帥,于是讓小帥還錢(qián)。
小帥非常清楚數(shù)據(jù)庫(kù)的事物隔離機(jī)制,知道你處于 RU 的事務(wù)隔離級(jí)別。于是說(shuō)馬上還你錢(qián),這時(shí)出現(xiàn)了以下情況:
圖片
- 小帥:開(kāi)啟事務(wù) A,給你轉(zhuǎn)錢(qián) 500,事務(wù)未提交;
- 你:開(kāi)啟事務(wù) B,查詢余額,發(fā)現(xiàn)余額已經(jīng)加了 500,于是把小帥的借條撕掉,并準(zhǔn)備給主播打賞;
- 小帥:看到借條已經(jīng)沒(méi)了,于是撤銷(xiāo)事務(wù) A。他的錢(qián)一分沒(méi)少,而你只讀到了他事務(wù) A 里的余額,但是真實(shí)的余額沒(méi)有增加,即發(fā)生了臟讀;
- 你:打賞付款時(shí)余額不足,損失了價(jià)值 500 塊錢(qián)的借條。
你非常失望,打算和小帥絕交,然后繼續(xù)學(xué)習(xí)剩下的隔離機(jī)制,看看怎么避免臟讀發(fā)生。
RC - 讀已提交(Read committed)
該隔離級(jí)別在一個(gè)事務(wù)進(jìn)行數(shù)據(jù)寫(xiě)入時(shí),不允許別的事務(wù)對(duì)該行數(shù)據(jù)進(jìn)行訪問(wèn)(包括讀寫(xiě))。這樣就可以保證事務(wù)讀到的數(shù)據(jù)一定是已經(jīng)提交了的,解決了臟讀的問(wèn)題。
但是 RC 會(huì)出現(xiàn)不可重復(fù)讀的問(wèn)題,比如:事務(wù) A 需要讀取兩次數(shù)據(jù),在讀取完第一次數(shù)據(jù)后,有另一個(gè)事務(wù) B 對(duì)該數(shù)據(jù)進(jìn)行的更新并提交事務(wù)。
此時(shí)事務(wù) A 再次讀取該數(shù)據(jù)時(shí),數(shù)據(jù)已經(jīng)發(fā)生了改變,即事務(wù)中兩次讀取的數(shù)據(jù)不一致。
小帥為了挽回友情,給你轉(zhuǎn)了 520 塊錢(qián),但是他覺(jué)得只還你 500 塊就可以,所以讓你還他 20 塊錢(qián)。
你這會(huì)忙著看美女主播,沒(méi)有時(shí)間轉(zhuǎn)錢(qián),他建議你把銀行卡的賬號(hào)密碼告訴他,他只轉(zhuǎn) 20。
為了保險(xiǎn)起見(jiàn),你打開(kāi)了一個(gè)事務(wù)去查詢銀行卡余額,并告訴了小帥密碼,接下來(lái)發(fā)生了如下場(chǎng)景:
- 你:開(kāi)啟事務(wù) A,查詢銀行卡余額為 820;
- 小帥:開(kāi)啟事務(wù) B,提款 800,并提交了事務(wù) B;
- 你:在事務(wù) A 中再次查詢余額時(shí),發(fā)現(xiàn)銀行卡只有 20 塊錢(qián)了,發(fā)生了不可重復(fù)讀。
不僅被借的錢(qián)沒(méi)拿到,又損失了 280 塊錢(qián),你越想越氣,罵了小帥一頓。然后繼續(xù)學(xué)習(xí)隔離機(jī)制,看看怎么防止不可重復(fù)讀的問(wèn)題。
RR - 可重復(fù)讀( Repeatable read)
在同一個(gè)事務(wù)內(nèi),多次讀取同一個(gè)數(shù)據(jù),在這個(gè)事務(wù)還未結(jié)束時(shí),其他事務(wù)不能訪問(wèn)該數(shù)據(jù)(包括讀寫(xiě))。
這種隔離級(jí)別下解決了臟讀和不可重復(fù)讀的問(wèn)題,但是可能會(huì)出現(xiàn)幻讀。
如事務(wù) A 在多次讀取數(shù)據(jù)時(shí),有另一個(gè)事務(wù) B 在數(shù)據(jù)行中間插入或刪除了數(shù)據(jù),此時(shí)事務(wù) A 再次讀取時(shí),可能會(huì)發(fā)現(xiàn)數(shù)據(jù)的行數(shù)變了。
簡(jiǎn)單來(lái)說(shuō),RR - 可重復(fù)讀可以保證當(dāng)前事務(wù)不會(huì)讀取到其他事務(wù)已提交的 update 操作,但無(wú)法感知其他事務(wù)的 insert 和 delete 操作。
小帥知道你不會(huì)再借錢(qián)了,還被你罵了一頓,心中不忿。就想著用你的銀行賬號(hào)搞事情,于是發(fā)生了接下來(lái)的場(chǎng)景:
- 你:開(kāi)啟事務(wù) A,想查詢一下剛才交易了幾次,事務(wù)里看到結(jié)果是 2 次;
- 小帥:開(kāi)啟事務(wù) B,發(fā)現(xiàn)已經(jīng)不能修改你的余額數(shù)據(jù),就索性往你的銀行卡里面寫(xiě)入了 100 次交易記錄,交易金額高達(dá)數(shù)千萬(wàn),提交事務(wù) B;
- 你:在事務(wù) A 里面繼續(xù)查詢交易次數(shù),發(fā)現(xiàn)變成了 102 次;
這時(shí),警察叔叔找上門(mén)了,說(shuō)有人舉報(bào)你惡意洗黑錢(qián),需要協(xié)助調(diào)查一下。
還好,經(jīng)過(guò)一番解釋和通過(guò)銀行數(shù)據(jù)庫(kù)的日志調(diào)查,發(fā)現(xiàn)是有人惡意篡改交易記錄,你平安無(wú)事回到了家。
這時(shí),你痛定思痛,驚覺(jué)交友不慎!于是沉下心來(lái)繼續(xù)學(xué)習(xí)隔離機(jī)制。
可串行化(Serializable)
該隔離級(jí)別下,事務(wù)只能依次執(zhí)行,解決了臟讀、不可重復(fù)讀和幻讀的問(wèn)題。但是代價(jià)較高,性能很低,一般很少使用。
在這種情況下,每次有觀眾和你一樣想給主播打賞,都需要排隊(duì)等候,直到前面的交易事務(wù)完全結(jié)束。
這時(shí),你了解到事務(wù)的奇妙和隔離的重要,于是打算好好學(xué)習(xí)數(shù)據(jù)庫(kù),不再看美女主播跳舞了。

而小帥,卻迷失在面向局子編程的路上越走越遠(yuǎn)。
4. 小結(jié)
我們總結(jié)一下,數(shù)據(jù)庫(kù)通過(guò)隔離級(jí)別解決了事務(wù)并發(fā)出現(xiàn)的各種問(wèn)題:
- RU,讀未提交解決了臟寫(xiě)問(wèn)題,但可能出現(xiàn)臟讀;
- RC,讀已提交解決了臟讀問(wèn)題,但可能出現(xiàn)不可重復(fù)讀;
- RR,可重復(fù)讀解決了不可重復(fù)讀的問(wèn)題,但可能出現(xiàn)幻讀;
- Serializable,串行化解決了幻讀的問(wèn)題,但性能很低。
MySQL 是怎么實(shí)現(xiàn)事務(wù)隔離性的呢?
答案是加鎖。事務(wù)級(jí)別越高,解決的并發(fā)事務(wù)問(wèn)題越多,同時(shí)也意味著加的鎖就越多。
鎖的個(gè)數(shù)對(duì)比:RU-讀未提交 < RC-讀已提交 < RR-可重復(fù)讀 < Serializable-串行化。
但是,頻繁的加鎖可能會(huì)導(dǎo)致讀取數(shù)據(jù)的時(shí)候沒(méi)辦法修改,修改數(shù)據(jù)的時(shí)候沒(méi)辦法讀取,極大的降低了數(shù)據(jù)庫(kù)讀寫(xiě)性能,就像串行化的隔離級(jí)別那樣。
所以,為了權(quán)衡數(shù)據(jù)安全和性能,MySQL 數(shù)據(jù)庫(kù)默認(rèn)使用的是 RR,即可重復(fù)讀的隔離級(jí)別。
































