Java面試題~MySQL實(shí)戰(zhàn)系列之4種事務(wù)隔離級(jí)別
作者:
修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
摘要:
對(duì)于關(guān)系型數(shù)據(jù)庫(kù)MySQL,想必各位小伙伴并不陌生,恰逢金九銀十、跳槽漲薪之際,Debug特意給諸位小伙伴整理了一些關(guān)于MySQL方面的知識(shí)點(diǎn),一來(lái)是加深鞏固MySQL技術(shù)棧,二來(lái)可以在跳槽時(shí)的面試提供一些助攻;這些知識(shí)點(diǎn)將分2-3篇系列文章進(jìn)行發(fā)布,感興趣的小伙伴可以持續(xù)關(guān)注哦~
本文我們將從MySQL的幾種事務(wù)隔離級(jí)別開始擼起,從MySQL事務(wù)的概念講起,再到事務(wù)的4個(gè)基本特性、事務(wù)并發(fā)時(shí)產(chǎn)生的問(wèn)題,最后再到MySQL的4種事務(wù)隔離級(jí)別的介紹以及對(duì)應(yīng)的實(shí)戰(zhàn)。
內(nèi)容:
一、事務(wù)簡(jiǎn)介與4個(gè)基本特性
事務(wù)(Transaction)一詞是計(jì)算機(jī)術(shù)語(yǔ),一般指的是要做的或者所做的事情,在計(jì)算機(jī)編程領(lǐng)域指的是訪問(wèn)并更新數(shù)據(jù)庫(kù)中各個(gè)數(shù)據(jù)項(xiàng)的一個(gè)執(zhí)行單元,一般是由兩段核心命令之間執(zhí)行的全體操作組成,即事務(wù)開始“begin transaction”、事務(wù)結(jié)束“end transaction”;而在這兩段操作命令之間,常見(jiàn)的就有:提交事務(wù)commit 、回滾事務(wù)rollback 等等;
而事務(wù)有4個(gè)基本特性,即我們常說(shuō)的“酸性”:ACID,每個(gè)字母代表一個(gè)特性,具體包括:
(1)原子性(Atomicity):事務(wù)開始后所有操作,要么全部做完,要么全部不做,不可能停滯在中間環(huán)節(jié);事務(wù)執(zhí)行過(guò)程中出錯(cuò),會(huì)回滾到事務(wù)開始前的狀態(tài),所有的操作就像沒(méi)有發(fā)生過(guò)一樣;也就是說(shuō)事務(wù)是一個(gè)不可分割的整體,就像化學(xué)中學(xué)過(guò)的原子,是物質(zhì)構(gòu)成的基本單位;
(2)一致性(Consistency):事務(wù)開始前和結(jié)束后,數(shù)據(jù)庫(kù)的完整性約束沒(méi)有被破壞;比如A向B轉(zhuǎn)賬,不可能A扣了錢,B卻沒(méi)收到;
(3)隔離性(Isolation):同一時(shí)間,只允許一個(gè)事務(wù)請(qǐng)求同一數(shù)據(jù),不同的事務(wù)之間彼此沒(méi)有任何干擾;比如A正在從一張銀行卡中取錢,在A取錢的過(guò)程結(jié)束前,B不能向這張卡轉(zhuǎn)賬;
(4)持久性(Durability):事務(wù)完成后,事務(wù)對(duì)數(shù)據(jù)庫(kù)的所有更新將被保存到數(shù)據(jù)庫(kù),不能回滾;
二、事務(wù)并發(fā)時(shí)產(chǎn)生的問(wèn)題
從上述中我們已經(jīng)知曉了“事務(wù)”其實(shí)就是一個(gè)或者一組操作單元,如“新增用戶信息”、
“更新用戶信息同時(shí)更新用戶頭像”等等均為實(shí)際項(xiàng)目開發(fā)中常見(jiàn)的事務(wù),這一事務(wù)涉及到的相關(guān)操作要么全部都做完、要么全部都不做;
然而在某些特殊的場(chǎng)景下,比如多線程并發(fā)操作同個(gè)數(shù)據(jù)庫(kù)表 或者 同份數(shù)據(jù),則有可能會(huì)出現(xiàn)事務(wù)的并發(fā)問(wèn)題,這些問(wèn)題可以歸結(jié)為3類,即:
(1)臟讀:事務(wù)A讀取了事務(wù)B更新的數(shù)據(jù),然后B執(zhí)行回滾操作,此時(shí)A讀取到的數(shù)據(jù)即是臟數(shù)據(jù);
(2)不可重復(fù)讀:事務(wù) A 多次讀取同一份數(shù)據(jù),事務(wù) B 在事務(wù)A多次讀取的過(guò)程中,對(duì)數(shù)據(jù)作了更新并提交,導(dǎo)致事務(wù)A多次讀取同一份數(shù)據(jù)時(shí),得到的結(jié)果不一致;
(3)幻讀:管理員A將數(shù)據(jù)庫(kù)中所有學(xué)生的成績(jī)從具體分?jǐn)?shù)改為ABCDE等級(jí),但是管理員B就在這個(gè)時(shí)候插入了一條具體分?jǐn)?shù)的記錄,當(dāng)系統(tǒng)管理員A修改結(jié)束后發(fā)現(xiàn)還有一條記錄沒(méi)有改過(guò)來(lái),就好像發(fā)生了幻覺(jué)一樣,這就叫幻讀。
附注:不可重復(fù)讀的 和 幻讀很容易混淆,不可重復(fù)讀側(cè)重于修改,幻讀側(cè)重于新增或刪除。解決不可重復(fù)讀的問(wèn)題只需鎖住滿足條件的行,而解決幻讀需要鎖表
三、4種事務(wù)隔離級(jí)別
既然數(shù)據(jù)庫(kù)在使用過(guò)程中有可能會(huì)出現(xiàn)事務(wù)并發(fā)的各種問(wèn)題,那么自然而然我們需要去尋找相應(yīng)的解決方案;幸運(yùn)的是,目前各大主流的數(shù)據(jù)庫(kù)如MySQL、Oracle、Sql Server等等本身就自帶了相應(yīng)的解決方案,即傳說(shuō)中的“事務(wù)隔離級(jí)別”;
下面我們以MySQL為例,介紹它的4種事務(wù)隔離級(jí)別,每一種均用于解決上述事務(wù)并發(fā)時(shí)產(chǎn)生的各種問(wèn)題,如下表格所示:
事務(wù)隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
讀未提交 | 可能出現(xiàn) | 可能出現(xiàn) | 可能出現(xiàn) |
讀已提交 | 不可能出現(xiàn) | 可能出現(xiàn) | 可能出現(xiàn) |
可重復(fù)讀 | 不可能出現(xiàn) | 不可能出現(xiàn) | 可能出現(xiàn) |
串行化 (serializable) | 不可能出現(xiàn) | 不可能出現(xiàn) | 不可能出現(xiàn) |
(1)讀未提交(read-uncommitted):顧名思義,指的是并發(fā)的事務(wù)A、B在操作同一份數(shù)據(jù)時(shí)由于先后順序、事務(wù)沒(méi)提交以及回滾等原因?qū)е伦罱K數(shù)據(jù)不一致性的現(xiàn)象;
比如變量a初始值為1,即a=1,事務(wù)A先讀取到變量a,此時(shí)Aa=1,在A準(zhǔn)備進(jìn)入后續(xù)相關(guān)操作之前,B事務(wù)對(duì)a變量執(zhí)行了減1操作,但是還沒(méi)有提交該事務(wù),此時(shí)在B看來(lái)a仍然為1,即Ba=1(因?yàn)檫€沒(méi)有提交“減1”操作對(duì)應(yīng)的事務(wù));
而出乎意料的是,此時(shí)A事務(wù)再次查詢變量a的值時(shí)會(huì)發(fā)現(xiàn)a已經(jīng)變?yōu)?了,即Aa=0(這就是所謂的“讀已提交”,而理論上應(yīng)該是1才對(duì),因?yàn)锽還沒(méi)提交“減1”操作對(duì)應(yīng)的事務(wù)),然后A帶著變量a=0進(jìn)行了后續(xù)的操作……
不幸的是,就在A帶著變量a=0的值進(jìn)行后續(xù)操作之前,如果此時(shí)B進(jìn)行回滾操作,即回滾“減1”操作,那么此時(shí)變量a將變?yōu)?,即最終a=1,但是為時(shí)已晚,A事務(wù)已經(jīng)拿著變量a=0的值去“瀟灑”了~,因此也就會(huì)出現(xiàn)“臟讀”、“不可重復(fù)讀”、“幻讀”,這一過(guò)程可以由下圖形象地表示:
(2)對(duì)于“讀未提交”事務(wù)隔離級(jí)別,很明顯目前許多主流的數(shù)據(jù)庫(kù)幾乎都不會(huì)使用;而如果想解決“讀未提交”事務(wù)隔離級(jí)別所出現(xiàn)的種種問(wèn)題,我們可以采用“讀已提交”的隔離級(jí)別進(jìn)行替代;
即“讀已提交”顧名思義指的是事務(wù)A最終從數(shù)據(jù)庫(kù)中讀取到的同份數(shù)據(jù)勢(shì)必為事務(wù)B“commit提交事務(wù)”后的數(shù)據(jù),這無(wú)疑可以解決“臟讀”的問(wèn)題,即“最終一致性”,但是由于事務(wù)A可能會(huì)發(fā)起多次“查詢操作”,每次操作執(zhí)行的間隔,可能會(huì)穿插進(jìn)B提交事務(wù),從而導(dǎo)致事務(wù)A首次查詢時(shí)變量a=1,而在后續(xù)發(fā)起相同的查詢操作時(shí)變量a確為0,即“幻讀”和“不可重復(fù)讀”的問(wèn)題依舊存在;
(3)“可重復(fù)讀”是MySQL默認(rèn)的事務(wù)隔離級(jí)別,可以解決“臟讀”、“不可重復(fù)讀”的問(wèn)題;
對(duì)于“臟讀”的問(wèn)題,在該事務(wù)隔離級(jí)別下,事務(wù)A與事務(wù)B分別執(zhí)行的操作可以說(shuō)是既獨(dú)立、又最終相互影響的,所謂的獨(dú)立,指的是事務(wù)不會(huì)讀到其他事務(wù)對(duì)已有數(shù)據(jù)的修改,即使其他事務(wù)已經(jīng)提交,也就是說(shuō),事務(wù)開始時(shí)讀到的數(shù)據(jù)是什么,在事務(wù)提交前的任意時(shí)刻,這些數(shù)據(jù)的值都是一樣的;而這也順帶解決了“不可重復(fù)讀”的問(wèn)題;
但是,對(duì)于其他事務(wù)如B新插入的數(shù)據(jù),事務(wù)A是可以讀到的,而這也就引發(fā)了幻讀問(wèn)題;(當(dāng)然啦,實(shí)際上由于MySQL底層的MVVC機(jī)制,這個(gè)問(wèn)題在實(shí)操時(shí)幾乎難以復(fù)現(xiàn))
值得一提的是,“可重復(fù)讀”這一事務(wù)隔離級(jí)別之所以可以起到相應(yīng)的作用,很多原因得得益于它的底層,即底層使用了MVCC機(jī)制,即多版本并發(fā)控制,即select操作不會(huì)更新版本號(hào),是快照讀(歷史版本);而insert、update和delete會(huì)更新版本號(hào),是當(dāng)前讀(當(dāng)前版本) ~ 找個(gè)時(shí)間我們?cè)偌?xì)談這玩意!
(4)而“串行化”這一事務(wù)隔離級(jí)別可以說(shuō)是4種事務(wù)隔離級(jí)別中隔離效果最好的,解決了臟讀、可重復(fù)讀、幻讀的問(wèn)題,但是效果最差,它因?yàn)閷⑹聞?wù)的執(zhí)行變?yōu)轫樞驁?zhí)行,與其他三個(gè)隔離級(jí)別相比,它就相當(dāng)于單線程的了,后一個(gè)事務(wù)的執(zhí)行必須等待前一個(gè)事務(wù)結(jié)束;這家伙在這里就不做過(guò)多介紹了!
四、簡(jiǎn)單比較
(1)首先是“讀未提交”,它是這4個(gè)當(dāng)中性能最好、但也可以說(shuō)是最野蠻的方式,因?yàn)樗皼](méi)心沒(méi)肺”、壓根兒就不加鎖,所以根本談不上什么隔離效果,可以理解為沒(méi)有隔離;
(2)再來(lái)說(shuō)說(shuō)“串行化”,讀的時(shí)候加共享鎖,也就是其他事務(wù)可以并發(fā)讀,但是不能寫,寫的時(shí)候加排它鎖,其他事務(wù)不能并發(fā)寫也不能并發(fā)讀,即淪落為“單線程”執(zhí)行了!
(3)最后是“讀已提交”和“可重復(fù)讀”,這兩種隔離級(jí)別算是比較復(fù)雜的,既要允許一定的并發(fā),又想要兼顧解決相應(yīng)的問(wèn)題。
五、總結(jié)
至此,我們已經(jīng)初步介紹完成了MySQL關(guān)于事務(wù)以及事務(wù)隔離級(jí)別方面的知識(shí)要點(diǎn),值得一提的是,MySQL只有InnoDB引擎才支持事務(wù),其中可重復(fù)讀是默認(rèn)的隔離級(jí)別;
“讀未提交”和“串行化”基本上是不需要考慮的隔離級(jí)別,因?yàn)榍罢邘缀跸喈?dāng)于“不加鎖”,后者則相當(dāng)于單線程執(zhí)行,效率幾乎很差;
相對(duì)于“讀未提交”而言,“讀已提交”級(jí)別則可以解決臟讀的問(wèn)題,它提供的“行鎖”機(jī)制可以解決并發(fā)更新的問(wèn)題;
而“可重復(fù)讀(repeatable-read)”級(jí)別則包含了“讀已提交”的優(yōu)點(diǎn),即同樣可以解決“臟讀”的問(wèn)題;而并“幻讀”的問(wèn)題則主要是通過(guò)行鎖和間隙鎖的組合 Next-Key 鎖來(lái)實(shí)現(xiàn)的;
六、彩蛋