MySQL事務(wù)隔離級(jí)別詳解

一、MySQL 事務(wù)

MySQL 事務(wù)指在InnoDB引擎下,MyISAM引擎不支持事務(wù)的。

數(shù)據(jù)庫(kù)事務(wù)指的是一組數(shù)據(jù)操作,事務(wù)內(nèi)的操作要么就是全部成功,要么就是全部失敗。

事務(wù)具有原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)四個(gè)特性,簡(jiǎn)稱 ACID。

二、MySQL事務(wù)并發(fā)問(wèn)題

臟讀
臟讀指的是讀到了其他事務(wù)未提交的數(shù)據(jù),未提交意味著這些數(shù)據(jù)可能會(huì)回滾,也就是可能最終不會(huì)存到數(shù)據(jù)庫(kù)中,也就是不存在的數(shù)據(jù)。這就是臟讀。

可重復(fù)讀
可重復(fù)讀指的是在一個(gè)事務(wù)內(nèi),最開(kāi)始讀到的數(shù)據(jù)和事務(wù)結(jié)束前的任意時(shí)刻讀到的同一批數(shù)據(jù)都是一致的。通常針對(duì)數(shù)據(jù)更新(UPDATE)操作。

不可重復(fù)讀
對(duì)比可重復(fù)讀,不可重復(fù)讀指的是在同一事務(wù)內(nèi),不同的時(shí)刻讀到的同一批數(shù)據(jù)可能是不一樣的,可能會(huì)受到其他事務(wù)的影響,比如其他事務(wù)改了這批數(shù)據(jù)并提交了。通常針對(duì)數(shù)據(jù)更新(UPDATE)操作。

幻讀
幻讀是針對(duì)數(shù)據(jù)插入(INSERT)操作來(lái)說(shuō)的。假設(shè)事務(wù)A對(duì)某些行的內(nèi)容作了更改,但是還未提交,此時(shí)事務(wù)B插入了與事務(wù)A更改前的記錄相同的記錄行,并且在事務(wù)A提交之前先提交了,而這時(shí),在事務(wù)A中查詢,會(huì)發(fā)現(xiàn)好像剛剛的更改對(duì)于某些數(shù)據(jù)未起作用,讓用戶感覺(jué)出現(xiàn)了幻覺(jué),這就叫幻讀

三、事務(wù)隔離級(jí)別

SQL 標(biāo)準(zhǔn)定義了四種隔離級(jí)別,MySQL 全都支持。這四種隔離級(jí)別分別是:
1.讀未提交(READ UNCOMMITTED)
2.讀已提交 (READ COMMITTED)
3.可重復(fù)讀 (REPEATABLE READ)
4.串行化 (SERIALIZABLE)

MySQL 的默認(rèn)級(jí)別:可重復(fù)讀。

事務(wù)隔離其實(shí)就是為了解決上面提到的臟讀、不可重復(fù)讀、幻讀等問(wèn)題

image.png

四、具體示例

查看MySQL默認(rèn)的隔離級(jí)別:


image.png

修改隔離級(jí)別的語(yǔ)句是:set [作用域] transaction isolation level [事務(wù)隔離級(jí)別],
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}。

其中作用于可以是 SESSION 或者 GLOBAL,GLOBAL 是全局的,而 SESSION 只針對(duì)當(dāng)前回話窗口。隔離級(jí)別是 {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} 這四種,不區(qū)分大小寫(xiě)。

MySQL 中執(zhí)行事務(wù)

事務(wù)的執(zhí)行過(guò)程如下,以 begin 或者 start transaction 開(kāi)始,然后執(zhí)行一系列操作,最后要執(zhí)行 commit 操作,事務(wù)才算結(jié)束。當(dāng)然,如果進(jìn)行回滾操作(rollback),事務(wù)也會(huì)結(jié)束。

image

需要注意的是,begin 命令并不代表事務(wù)的開(kāi)始,事務(wù)開(kāi)始于 begin 命令之后的第一條語(yǔ)句執(zhí)行的時(shí)候。例如下面示例中,select * from xxx 才是事務(wù)的開(kāi)始,

另外,通過(guò)以下語(yǔ)句可以查詢當(dāng)前有多少事務(wù)正在運(yùn)行。

select * from information_schema.innodb_trx;

下面開(kāi)始驗(yàn)證各種隔離級(jí)別。

準(zhǔn)備一張表test,表中只有一條記錄:


image.png

讀未提交

MySQL 事務(wù)隔離其實(shí)是依靠來(lái)實(shí)現(xiàn)的,加鎖自然會(huì)帶來(lái)性能的損失。而讀未提交隔離級(jí)別是不加鎖的,所以它的性能是最好的,沒(méi)有加鎖、解鎖帶來(lái)的性能開(kāi)銷。但有利就有弊,這基本上就相當(dāng)于裸奔啊,所以它連臟讀的問(wèn)題都沒(méi)辦法解決。

任何事務(wù)對(duì)數(shù)據(jù)的修改都會(huì)第一時(shí)間暴露給其他事務(wù),即使事務(wù)還沒(méi)有提交。

首先設(shè)置全局隔離級(jí)別為讀未提交。

set global transaction isolation level read uncommitted;

設(shè)置完成后,只對(duì)之后新起的 session 才起作用,對(duì)已經(jīng)啟動(dòng) session 無(wú)效。


image.png

讀未提交,其實(shí)就是可以讀到其他事務(wù)未提交的數(shù)據(jù),但沒(méi)有辦法保證你讀到的數(shù)據(jù)最終一定是提交后的數(shù)據(jù),如果中間發(fā)生回滾,那就會(huì)出現(xiàn)臟數(shù)據(jù)問(wèn)題,讀未提交沒(méi)辦法解決臟數(shù)據(jù)問(wèn)題。更別提可重復(fù)讀和幻讀了。

image.png

讀已提交

既然讀未提交沒(méi)辦法解決臟數(shù)據(jù)問(wèn)題,那么就有了讀已提交。讀已提交就是一個(gè)事務(wù)只能讀到其他事務(wù)已經(jīng)提交過(guò)的數(shù)據(jù),也就是其他事務(wù)調(diào)用 commit 命令之后的數(shù)據(jù)。那臟數(shù)據(jù)問(wèn)題迎刃而解了。

set global transaction isolation level read committed;
image.png

我們發(fā)現(xiàn),當(dāng)前是讀已提交的隔離級(jí)別,即使事務(wù)1進(jìn)行了更新操作,但還沒(méi)有commit,另一個(gè)事務(wù)是讀取不到更新后的結(jié)果的。

然后我們進(jìn)行commit,看看另一個(gè)事務(wù)能否查到最新的結(jié)果。


image.png

從上圖中,我們可以看出,當(dāng)事務(wù)1提交事務(wù)之后,更新后的結(jié)果就可以在另一個(gè)事務(wù)中查詢的到了。

這就出現(xiàn)了一個(gè)問(wèn)題,在同一事務(wù)中(本例中的事務(wù)2),事務(wù)的不同時(shí)刻同樣的查詢條件,查詢出來(lái)的記錄內(nèi)容是不一樣的,事務(wù)1的提交影響了事務(wù)2的查詢結(jié)果,這就是不可重復(fù)讀,也就是讀已提交隔離級(jí)別。

可重復(fù)讀

可重復(fù)是對(duì)比不可重復(fù)而言的,上面說(shuō)不可重復(fù)讀是指同一事物不同時(shí)刻讀到的數(shù)據(jù)值可能不一致。而可重復(fù)讀是指,事務(wù)不會(huì)讀到其他事務(wù)對(duì)已有數(shù)據(jù)的修改,即使其他事務(wù)已提交,也就是說(shuō),事務(wù)開(kāi)始時(shí)讀到的已有數(shù)據(jù)是什么,在事務(wù)提交前的任意時(shí)刻,這些數(shù)據(jù)的值都是一樣的。但是,對(duì)于其他事務(wù)新插入的數(shù)據(jù)是可以讀到的,這也就引發(fā)了幻讀問(wèn)題。

同樣的,需改全局隔離級(jí)別為可重復(fù)讀級(jí)別。

set global transaction isolation level repeatable read;
image.png

首先看一下可重復(fù)讀的效果,事務(wù)1啟動(dòng)后修改了數(shù)據(jù),并且在事務(wù)2之前提交,事務(wù)2在事務(wù)開(kāi)始和事務(wù)1提交之后兩個(gè)時(shí)間節(jié)點(diǎn)都讀取的數(shù)據(jù)相同,已經(jīng)可以看出可重復(fù)讀的效果。

可重復(fù)讀做到了,這只是針對(duì)已有行的更改操作有效,但是對(duì)于新插入的行記錄,就沒(méi)這么幸運(yùn)了,幻讀就這么產(chǎn)生了。


image.png

但是,測(cè)試的時(shí)候發(fā)現(xiàn)并不能插入新紀(jì)錄成功,這是因?yàn)镸ySQL的可重復(fù)讀已經(jīng)解決了幻讀的問(wèn)題,后面再詳細(xì)介紹。

串行化

串行化是4種事務(wù)隔離級(jí)別中隔離效果最好的,解決了臟讀、可重復(fù)讀、幻讀的問(wèn)題,但是效果最差,它將事務(wù)的執(zhí)行變?yōu)轫樞驁?zhí)行,與其他三個(gè)隔離級(jí)別相比,它就相當(dāng)于單線程,后一個(gè)事務(wù)的執(zhí)行必須等待前一個(gè)事務(wù)結(jié)束。

MySQL 中是如何實(shí)現(xiàn)事務(wù)隔離的

首先說(shuō)讀未提交,它是性能最好,也可以說(shuō)它是最野蠻的方式,因?yàn)樗鼔焊鶅壕筒患渔i,所以根本談不上什么隔離效果,可以理解為沒(méi)有隔離。

再來(lái)說(shuō)串行化。讀的時(shí)候加共享鎖,也就是其他事務(wù)可以并發(fā)讀,但是不能寫(xiě)。寫(xiě)的時(shí)候加排它鎖,其他事務(wù)不能并發(fā)寫(xiě)也不能并發(fā)讀。

最后說(shuō)讀已提交和可重復(fù)讀。這兩種隔離級(jí)別是比較復(fù)雜的,既要允許一定的并發(fā),又想要兼顧的解決問(wèn)題。

實(shí)現(xiàn)可重復(fù)讀

為了解決不可重復(fù)讀,或者為了實(shí)現(xiàn)可重復(fù)讀,MySQL 采用了 MVVC (多版本并發(fā)控制) 的方式。

我們?cè)跀?shù)據(jù)庫(kù)表中看到的一行記錄可能實(shí)際上有多個(gè)版本,每個(gè)版本的記錄除了有數(shù)據(jù)本身外,還要有一個(gè)表示版本的字段,記為 row trx_id,而這個(gè)字段就是使其產(chǎn)生的事務(wù)的 id,事務(wù) ID 記為 transaction id,它在事務(wù)開(kāi)始的時(shí)候向事務(wù)系統(tǒng)申請(qǐng),按時(shí)間先后順序遞增。


image.png

按照上面這張圖理解,一行記錄現(xiàn)在有 3 個(gè)版本,每一個(gè)版本都記錄這使其產(chǎn)生的事務(wù) ID,比如事務(wù)A的transaction id 是100,那么版本1的row trx_id 就是 100,同理版本2和版本3。

在上面介紹讀已提交和可重復(fù)讀的時(shí)候都提到了一個(gè)詞,叫做快照,學(xué)名叫做一致性視圖,這也是可重復(fù)讀和不可重復(fù)讀的關(guān)鍵,可重復(fù)讀是在事務(wù)開(kāi)始的時(shí)候生成一個(gè)當(dāng)前事務(wù)全局性的快照,而讀提交則是每次執(zhí)行語(yǔ)句的時(shí)候都重新生成一次快照。

對(duì)于一個(gè)快照來(lái)說(shuō),它能夠讀到那些版本數(shù)據(jù),要遵循以下規(guī)則:

  1. 當(dāng)前事務(wù)內(nèi)的更新,可以讀到;
  2. 版本未提交,不能讀到;
  3. 版本已提交,但是卻在快照創(chuàng)建后提交的,不能讀到;
  4. 版本已提交,且是在快照創(chuàng)建前提交的,可以讀到;
    利用上面的規(guī)則,再返回去套用到讀提交和可重復(fù)讀的那兩張圖上就很清晰了。還是要強(qiáng)調(diào),兩者主要的區(qū)別就是在快照的創(chuàng)建上,可重復(fù)讀僅在事務(wù)開(kāi)始是創(chuàng)建一次,而讀提交每次執(zhí)行語(yǔ)句的時(shí)候都要重新創(chuàng)建一次。

并發(fā)寫(xiě)問(wèn)題

兩個(gè)事務(wù),對(duì)同一條數(shù)據(jù)做修改。最后結(jié)果應(yīng)該是哪個(gè)事務(wù)的結(jié)果呢,肯定要是時(shí)間靠后的那個(gè)對(duì)不對(duì)。并且更新之前要先讀數(shù)據(jù),這里所說(shuō)的讀和上面說(shuō)到的讀不一樣,更新之前的讀叫做“當(dāng)前讀”,總是當(dāng)前版本的數(shù)據(jù),也就是多版本中最新一次提交的那版。

假設(shè)事務(wù)A執(zhí)行 update 操作, update 的時(shí)候要對(duì)所修改的行加行鎖,這個(gè)行鎖會(huì)在提交之后才釋放。而在事務(wù)A提交之前,事務(wù)B也想 update 這行數(shù)據(jù),于是申請(qǐng)行鎖,但是由于已經(jīng)被事務(wù)A占有,事務(wù)B是申請(qǐng)不到的,此時(shí),事務(wù)B就會(huì)一直處于等待狀態(tài),直到事務(wù)A提交,事務(wù)B才能繼續(xù)執(zhí)行,如果事務(wù)A的時(shí)間太長(zhǎng),那么事務(wù)B很有可能出現(xiàn)超時(shí)異常。

加鎖的過(guò)程要分有索引和無(wú)索引兩種情況,比如下面這條語(yǔ)句

update user set age=11 where id = 1

id 是這張表的主鍵,是有索引的情況,那么 MySQL 直接就在索引數(shù)中找到了這行數(shù)據(jù),然后干凈利落的加上行鎖就可以了。

而下面這條語(yǔ)句

update user set age=11 where age=10

表中并沒(méi)有為 age 字段設(shè)置索引,所以, MySQL 無(wú)法直接定位到這行數(shù)據(jù)。那怎么辦呢,當(dāng)然也不是加表鎖了。MySQL 會(huì)為這張表中所有行加行鎖,沒(méi)錯(cuò),是所有行。但是呢,在加上行鎖后,MySQL 會(huì)進(jìn)行一遍過(guò)濾,發(fā)現(xiàn)不滿足的行就釋放鎖,最終只留下符合條件的行。雖然最終只為符合條件的行加了鎖,但是這一鎖一釋放的過(guò)程對(duì)性能也是影響極大的。所以,如果是大表的話,建議合理設(shè)計(jì)索引,如果真的出現(xiàn)這種情況,那很難保證并發(fā)度。

解決幻讀

并發(fā)寫(xiě)問(wèn)題的解決方式就是行鎖,而解決幻讀用的也是鎖,叫做間隙鎖,MySQL 把行鎖和間隙鎖合并在一起,解決了并發(fā)寫(xiě)和幻讀的問(wèn)題,這個(gè)鎖叫做 Next-Key鎖。

假設(shè)現(xiàn)在表中有兩條記錄,并且 age 字段已經(jīng)添加了索引,兩條記錄 age 的值分別為 10 和 30。

此時(shí),在數(shù)據(jù)庫(kù)中會(huì)為索引維護(hù)一套B+樹(shù),用來(lái)快速定位行記錄。B+索引樹(shù)是有序的,所以會(huì)把這張表的索引分割成幾個(gè)區(qū)間。

image

如圖所示,分成了3 個(gè)區(qū)間,(負(fù)無(wú)窮,10]、(10,30]、(30,正無(wú)窮],在這3個(gè)區(qū)間是可以加間隙鎖的。

在事務(wù)1提交之前,事務(wù)B的插入操作只能等待,這就是間隙鎖起得作用。當(dāng)事務(wù)1執(zhí)行update testset name='zhao123’ where age = 30; 的時(shí)候,由于條件 where age =30 ,數(shù)據(jù)庫(kù)不僅在 age =30 的行上添加了行鎖,而且在這條記錄的兩邊,也就是(10,30], (30,正無(wú)窮)這兩個(gè)區(qū)間加了間隙鎖,從而導(dǎo)致事務(wù)2插入操作無(wú)法完成,只能等待事務(wù)1提交。不僅插入 age = 30 的記錄需要等待事務(wù)1提交,10<age<30、30<age 的記錄頁(yè)無(wú)法完成,而小于10的記錄則不受影響,這足以解決幻讀問(wèn)題了。

這是有索引的情況,如果 age 不是索引列,那么數(shù)據(jù)庫(kù)會(huì)為整個(gè)表加上間隙鎖。所以,如果是沒(méi)有索引的話,不管 age 是否大于等于30,都要等待事務(wù)1提交才可以成功插入。

總結(jié)

MySQL 的 InnoDB 引擎才支持事務(wù),其中可重復(fù)讀是默認(rèn)的隔離級(jí)別。

讀未提交和串行化基本上是不需要考慮的隔離級(jí)別,前者不加鎖限制,后者相當(dāng)于單線程執(zhí)行,效率太差。

讀已提交解決了臟讀問(wèn)題,行鎖解決了并發(fā)更新的問(wèn)題。并且 MySQL 在可重復(fù)讀級(jí)別解決了幻讀問(wèn)題,是通過(guò)行鎖和間隙鎖的組合 Next-Key 鎖實(shí)現(xiàn)的。

?著作權(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)容