MySQL事務(wù)隔離級(jí)別和實(shí)現(xiàn)原理,看這一篇就夠了!!!

MySQL事務(wù)隔離級(jí)別和實(shí)現(xiàn)原理(看這一篇文章就夠了:取)

經(jīng)常提到數(shù)據(jù)庫(kù)的事務(wù),那你知道數(shù)據(jù)庫(kù)還有事務(wù)隔離的說(shuō)法嗎面粮,事務(wù)隔離還有隔離級(jí)別少孝,那什么是事務(wù)隔離,隔離級(jí)別又是什么呢熬苍?本文就幫大家梳理一下稍走。

MySQL 事務(wù)

本文所說(shuō)的 MySQL 事務(wù)都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事務(wù)的柴底。

數(shù)據(jù)庫(kù)事務(wù)指的是一組數(shù)據(jù)操作婿脸,事務(wù)內(nèi)的操作要么就是全部成功,要么就是全部失敗柄驻,什么都不做狐树,其實(shí)不是沒(méi)做,是可能做了一部分但是只要有一步失敗鸿脓,就要回滾所有操作抑钟,有點(diǎn)一不做二不休的意思。

假設(shè)一個(gè)網(wǎng)購(gòu)付款的操作野哭,用戶(hù)付款后要涉及到訂單狀態(tài)更新在塔、扣庫(kù)存以及其他一系列動(dòng)作,這就是一個(gè)事務(wù)拨黔,如果一切正常那就相安無(wú)事蛔溃,一旦中間有某個(gè)環(huán)節(jié)異常,那整個(gè)事務(wù)就要回滾篱蝇,總不能更新了訂單狀態(tài)但是不扣庫(kù)存吧贺待,這問(wèn)題就大了。

事務(wù)具有原子性(Atomicity)零截、一致性(Consistency)狠持、隔離性(Isolation)、持久性(Durability)四個(gè)特性瞻润,簡(jiǎn)稱(chēng) ACID,缺一不可。今天要說(shuō)的就是隔離性绍撞。

概念說(shuō)明

以下幾個(gè)概念是事務(wù)隔離級(jí)別要實(shí)際解決的問(wèn)題正勒,所以需要搞清楚都是什么意思。

臟讀

臟讀指的是讀到了其他事務(wù)未提交的數(shù)據(jù)傻铣,未提交意味著這些數(shù)據(jù)可能會(huì)回滾章贞,也就是可能最終不會(huì)存到數(shù)據(jù)庫(kù)中,也就是不存在的數(shù)據(jù)非洲。讀到了并一定最終存在的數(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中查詢(xún)超歌,會(huì)發(fā)現(xiàn)好像剛剛的更改對(duì)于某些數(shù)據(jù)未起作用砍艾,但其實(shí)是事務(wù)B剛插入進(jìn)來(lái)的,讓用戶(hù)感覺(jué)很魔幻巍举,感覺(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)

從上往下,隔離強(qiáng)度逐漸增強(qiáng)炭分,性能逐漸變差桃焕。采用哪種隔離級(jí)別要根據(jù)系統(tǒng)需求權(quán)衡決定,其中捧毛,可重復(fù)讀是 MySQL 的默認(rèn)級(jí)別观堂。

事務(wù)隔離其實(shí)就是為了解決上面提到的臟讀让网、不可重復(fù)讀、幻讀這幾個(gè)問(wèn)題师痕,下面展示了 4 種隔離級(jí)別對(duì)這三個(gè)問(wèn)題的解決程度溃睹。

image

只有串行化的隔離級(jí)別解決了全部這 3 個(gè)問(wèn)題,其他的 3 個(gè)隔離級(jí)別都有缺陷胰坟。

一探究竟

下面因篇,我們來(lái)一一分析這 4 種隔離級(jí)別到底是怎么個(gè)意思。

如何設(shè)置隔離級(jí)別

我們可以通過(guò)以下語(yǔ)句查看當(dāng)前數(shù)據(jù)庫(kù)的隔離級(jí)別笔横,通過(guò)下面語(yǔ)句可以看出我使用的 MySQL 的隔離級(jí)別是 REPEATABLE-READ竞滓,也就是可重復(fù)讀,這也是 MySQL 的默認(rèn)級(jí)別吹缔。

# 查看事務(wù)隔離級(jí)別 5.7.20 之后
show variables like 'transaction_isolation';
SELECT @@transaction_isolation

# 5.7.20 之后
SELECT @@tx_isolation
show variables like 'tx_isolation'

+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+

稍后商佑,我們要修改數(shù)據(jù)庫(kù)的隔離級(jí)別,所以先了解一下具體的修改方式涛菠。

修改隔離級(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)前回話(huà)窗口。隔離級(jí)別是 {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE} 這四種迄薄,不區(qū)分大小寫(xiě)琅关。

比如下面這個(gè)語(yǔ)句的意思是設(shè)置全局隔離級(jí)別為讀提交級(jí)別。

mysql> set global transaction isolation level read committed;

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)始侯繁,

begin;
select * from xxx; 
commit; -- 或者 rollback;

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

select * from information_schema.innodb_trx;

好了贮竟,重點(diǎn)來(lái)了丽焊,開(kāi)始分析這幾個(gè)隔離級(jí)別了较剃。

接下來(lái)我會(huì)用一張表來(lái)做一下驗(yàn)證,表結(jié)構(gòu)簡(jiǎn)單如下:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  `age` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

初始只有一條記錄:

mysql> SELECT * FROM user;
+----+-----------------+------+
| id | name            | age  |
+----+-----------------+------+
|  1 | 古時(shí)的風(fēng)箏        |    1 |
+----+-----------------+------+

讀未提交

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

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

下面來(lái)做個(gè)簡(jiǎn)單實(shí)驗(yàn)驗(yàn)證一下纤子,首先設(shè)置全局隔離級(jí)別為讀未提交搬瑰。

set global transaction isolation level read uncommitted;

設(shè)置完成后,只對(duì)之后新起的 session 才起作用控硼,對(duì)已經(jīng)啟動(dòng) session 無(wú)效泽论。如果用 shell 客戶(hù)端那就要重新連接 MySQL,如果用 Navicat 那就要?jiǎng)?chuàng)建新的查詢(xún)窗口卡乾。

啟動(dòng)兩個(gè)事務(wù)翼悴,分別為事務(wù)A和事務(wù)B,在事務(wù)A中使用 update 語(yǔ)句幔妨,修改 age 的值為10鹦赎,初始是1 ,在執(zhí)行完 update 語(yǔ)句之后误堡,在事務(wù)B中查詢(xún) user 表古话,會(huì)看到 age 的值已經(jīng)是 10 了,這時(shí)候事務(wù)A還沒(méi)有提交锁施,而此時(shí)事務(wù)B有可能拿著已經(jīng)修改過(guò)的 age=10 去進(jìn)行其他操作了陪踩。在事務(wù)B進(jìn)行操作的過(guò)程中,很有可能事務(wù)A由于某些原因沾谜,進(jìn)行了事務(wù)回滾操作膊毁,那其實(shí)事務(wù)B得到的就是臟數(shù)據(jù)了,拿著臟數(shù)據(jù)去進(jìn)行其他的計(jì)算基跑,那結(jié)果肯定也是有問(wèn)題的婚温。

順著時(shí)間軸往表示兩事務(wù)中操作的執(zhí)行順序,重點(diǎn)看圖中 age 字段的值媳否。


image

讀未提交栅螟,其實(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ù)讀和幻讀了吃媒,想都不要想瓤介。

讀提交

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

讀提交事務(wù)隔離級(jí)別是大多數(shù)流行數(shù)據(jù)庫(kù)的默認(rèn)事務(wù)隔離界別祠斧,比如 Oracle,但是不是 MySQL 的默認(rèn)隔離界別拱礁。

我們繼續(xù)來(lái)做一下驗(yàn)證琢锋,首先把事務(wù)隔離級(jí)別改為讀提交級(jí)別。

set global transaction isolation level read committed;

之后需要重新打開(kāi)新的 session 窗口呢灶,也就是新的 shell 窗口才可以吴超。

同樣開(kāi)啟事務(wù)A和事務(wù)B兩個(gè)事務(wù),在事務(wù)A中使用 update 語(yǔ)句將 id=1 的記錄行 age 字段改為 10填抬。此時(shí)烛芬,在事務(wù)B中使用 select 語(yǔ)句進(jìn)行查詢(xún),我們發(fā)現(xiàn)在事務(wù)A提交之前飒责,事務(wù)B中查詢(xún)到的記錄 age 一直是1赘娄,直到事務(wù)A提交,此時(shí)在事務(wù)B中 select 查詢(xún)宏蛉,發(fā)現(xiàn) age 的值已經(jīng)是 10 了遣臼。

這就出現(xiàn)了一個(gè)問(wèn)題,在同一事務(wù)中(本例中的事務(wù)B)拾并,事務(wù)的不同時(shí)刻同樣的查詢(xún)條件揍堰,查詢(xún)出來(lái)的記錄內(nèi)容是不一樣的,事務(wù)A的提交影響了事務(wù)B的查詢(xún)結(jié)果嗅义,這就是不可重復(fù)讀奥吩,也就是讀提交隔離級(jí)別深纲。


image

每個(gè) select 語(yǔ)句都有自己的一份快照,而不是一個(gè)事務(wù)一份,所以在不同的時(shí)刻灭抑,查詢(xún)出來(lái)的數(shù)據(jù)可能是不一致的蔬咬。

讀提交解決了臟讀的問(wèn)題,但是無(wú)法做到可重復(fù)讀,也沒(méi)辦法解決幻讀式塌。

可重復(fù)讀

可重復(fù)是對(duì)比不可重復(fù)而言的,上面說(shuō)不可重復(fù)讀是指同一事物不同時(shí)刻讀到的數(shù)據(jù)值可能不一致友浸。而可重復(fù)讀是指峰尝,事務(wù)不會(huì)讀到其他事務(wù)對(duì)已有數(shù)據(jù)的修改,及時(shí)其他事務(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;

在這個(gè)隔離級(jí)別下,啟動(dòng)兩個(gè)事務(wù)疾捍,兩個(gè)事務(wù)同時(shí)開(kāi)啟奈辰。

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


image

可重復(fù)讀做到了瑟啃,這只是針對(duì)已有行的更改操作有效,但是對(duì)于新插入的行記錄揩尸,就沒(méi)這么幸運(yùn)了蛹屿,幻讀就這么產(chǎn)生了。我們看一下這個(gè)過(guò)程:

事務(wù)A開(kāi)始后岩榆,執(zhí)行 update 操作错负,將 age = 1 的記錄的 name 改為“風(fēng)箏2號(hào)”;

事務(wù)B開(kāi)始后勇边,在事務(wù)執(zhí)行完 update 后犹撒,執(zhí)行 insert 操作,插入記錄 age =1粒褒,name = 古時(shí)的風(fēng)箏识颊,這和事務(wù)A修改的那條記錄值相同,然后提交怀浆。

事務(wù)B提交后谊囚,事務(wù)A中執(zhí)行 select怕享,查詢(xún) age=1 的數(shù)據(jù),這時(shí)镰踏,會(huì)發(fā)現(xiàn)多了一行函筋,并且發(fā)現(xiàn)還有一條 name = 古時(shí)的風(fēng)箏,age = 1 的記錄奠伪,這其實(shí)就是事務(wù)B剛剛插入的跌帐,這就是幻讀。


image

要說(shuō)明的是绊率,當(dāng)你在 MySQL 中測(cè)試幻讀的時(shí)候谨敛,并不會(huì)出現(xiàn)上圖的結(jié)果,幻讀并沒(méi)有發(fā)生滤否,MySQL 的可重復(fù)讀隔離級(jí)別其實(shí)解決了幻讀問(wèn)題脸狸,這會(huì)在后面的內(nèi)容說(shuō)明

串行化

串行化是4種事務(wù)隔離級(jí)別中隔離效果最好的,解決了臟讀藐俺、可重復(fù)讀炊甲、幻讀的問(wèn)題,但是效果最差欲芹,它將事務(wù)的執(zhí)行變?yōu)轫樞驁?zhí)行卿啡,與其他三個(gè)隔離級(jí)別相比,它就相當(dāng)于單線(xiàn)程菱父,后一個(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

按照上面這張圖理解点待,一行記錄現(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í)異常拭抬。如下圖所示部默。


image

加鎖的過(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)不滿(mǎn)足的行就釋放鎖,最終只留下符合條件的行匣椰。雖然最終只為符合條件的行加了鎖裆熙,但是這一鎖一釋放的過(guò)程對(duì)性能也是影響極大的。所以禽笑,如果是大表的話(huà)入录,建議合理設(shè)計(jì)索引,如果真的出現(xiàn)這種情況佳镜,那很難保證并發(fā)度僚稿。

解決幻讀

上面介紹可重復(fù)讀的時(shí)候,那張圖里標(biāo)示著出現(xiàn)幻讀的地方實(shí)際上在 MySQL 中并不會(huì)出現(xiàn)蟀伸,MySQL 已經(jīng)在可重復(fù)讀隔離級(jí)別下解決了幻讀的問(wèn)題蚀同。

前面剛說(shuō)了并發(fā)寫(xiě)問(wèn)題的解決方式就是行鎖,而解決幻讀用的也是鎖啊掏,叫做間隙鎖蠢络,MySQL 把行鎖和間隙鎖合并在一起,解決了并發(fā)寫(xiě)和幻讀的問(wèn)題迟蜜,這個(gè)鎖叫做 Next-Key鎖刹孔。

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

image.png

此時(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ū)間是可以加間隙鎖的晴裹。

之后,我用下面的兩個(gè)事務(wù)演示一下加鎖過(guò)程救赐。


image

在事務(wù)A提交之前涧团,事務(wù)B的插入操作只能等待,這就是間隙鎖起得作用。當(dāng)事務(wù)A執(zhí)行update user set name='風(fēng)箏2號(hào)’ where age = 10; 的時(shí)候泌绣,由于條件 where age = 10 钮追,數(shù)據(jù)庫(kù)不僅在 age =10 的行上添加了行鎖,而且在這條記錄的兩邊阿迈,也就是(負(fù)無(wú)窮,10]元媚、(10,30]這兩個(gè)區(qū)間加了間隙鎖,從而導(dǎo)致事務(wù)B插入操作無(wú)法完成苗沧,只能等待事務(wù)A提交刊棕。不僅插入 age = 10 的記錄需要等待事務(wù)A提交,age<10待逞、10<age<30 的記錄頁(yè)無(wú)法完成甥角,而大于等于30的記錄則不受影響,這足以解決幻讀問(wèn)題了飒焦。

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

總結(jié)

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

讀未提交和串行化基本上是不需要考慮的隔離級(jí)別驰凛,前者不加鎖限制,后者相當(dāng)于單線(xiàn)程執(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)系作者
  • 序言:七十年代末燕垃,一起剝皮案震驚了整個(gè)濱河市枢劝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卜壕,老刑警劉巖您旁,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異轴捎,居然都是意外死亡鹤盒,警方通過(guò)查閱死者的電腦和手機(jī)蚕脏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)昨悼,“玉大人蝗锥,你說(shuō)我怎么就攤上這事÷蚀ィ” “怎么了终议?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)葱蝗。 經(jīng)常有香客問(wèn)我穴张,道長(zhǎng),這世上最難降的妖魔是什么两曼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任皂甘,我火速辦了婚禮,結(jié)果婚禮上悼凑,老公的妹妹穿的比我還像新娘偿枕。我一直安慰自己,他們只是感情好户辫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布渐夸。 她就那樣靜靜地躺著,像睡著了一般渔欢。 火紅的嫁衣襯著肌膚如雪墓塌。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天奥额,我揣著相機(jī)與錄音苫幢,去河邊找鬼。 笑死垫挨,一個(gè)胖子當(dāng)著我的面吹牛韩肝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播九榔,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼伞梯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了帚屉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漾峡,失蹤者是張志新(化名)和其女友劉穎攻旦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體生逸,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牢屋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年且预,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烙无。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锋谐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出截酷,到底是詐尸還是另有隱情涮拗,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布迂苛,位于F島的核電站三热,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏三幻。R本人自食惡果不足惜就漾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望念搬。 院中可真熱鬧抑堡,春花似錦、人聲如沸朗徊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荣倾。三九已至悯搔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舌仍,已是汗流浹背妒貌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铸豁,地道東北人灌曙。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像节芥,于是被迫代替她去往敵國(guó)和親在刺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349