溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

MySQL事務(wù)、隔離級(jí)別及MVCC是什么

發(fā)布時(shí)間:2020-10-29 10:08:33 來(lái)源:億速云 閱讀:158 作者:小新 欄目:MySQL數(shù)據(jù)庫(kù)

這篇文章主要介紹了MySQL事務(wù)、隔離級(jí)別及MVCC是什么,具有一定借鑒價(jià)值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。

mysql教程欄目介紹MySQL相關(guān)的事務(wù)、隔離級(jí)別及MVCC。

MySQL 系列的第四篇,主要內(nèi)容是事務(wù),包括事務(wù) ACID 特性,隔離級(jí)別,臟讀、不可重復(fù)讀、幻讀的理解以及多版本并發(fā)控制(MVCC)等內(nèi)容。

事務(wù)(Transaction)能夠保證一組不可分割的原子性操作集合要么都執(zhí)行,要么都不執(zhí)行。在MySQL 常用的存儲(chǔ)引擎中,InnoDB 是支持事務(wù)的,原生的 MyISAM 引擎則不支持事務(wù)。

在本文中,若未特殊說(shuō)明,使用的數(shù)據(jù)表及數(shù)據(jù)如下所示:

CREATE TABLE `user`  (  `id` int(11) DEFAULT NULL,  `name` varchar(12) DEFAULT NULL) ENGINE = InnoDB;insert into user values(1, '刺猬');復(fù)制代碼

1. ACID 四大特性

首先需要理解的是事務(wù) ACID 四大特性,即原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability),這也是事務(wù)的四個(gè)基本要素。

為了詳細(xì)解釋 ACID 特性,在這里先設(shè)想一個(gè)場(chǎng)景:我向你轉(zhuǎn)賬100元。

假設(shè)這個(gè)操作可以分為以下幾步(假設(shè)我和你的賬戶(hù)余額均為100元):

  1. 查詢(xún)我的賬戶(hù)余額
  2. 我的賬戶(hù)扣款100元
  3. 100元開(kāi)始轉(zhuǎn)移
  4. 查詢(xún)你的賬戶(hù)余額
  5. 你的賬戶(hù)到賬100元

1.1 原子性(Atomicity)

事務(wù)的原子性是指:一個(gè)事務(wù)必須是不可再分割的最小工作單元,一個(gè)事務(wù)中的操作要么都成功,要么都失敗,不可能存在只執(zhí)行一個(gè)事務(wù)中部分操作的情況。

在上述的轉(zhuǎn)賬場(chǎng)景中,原子性就要求了這五個(gè)步驟要么都執(zhí)行,要么都不執(zhí)行,不可能存在我的賬戶(hù)扣款100元,而你的賬戶(hù)100元沒(méi)有到賬的情況。

1.2 一致性(Consistency)

事務(wù)的一致性是指:數(shù)據(jù)庫(kù)總是從一個(gè)一致性狀態(tài)轉(zhuǎn)換到另一個(gè)一致性狀態(tài),一致性側(cè)重的是數(shù)據(jù)的可見(jiàn)性,數(shù)據(jù)的中間狀態(tài)對(duì)外是不可見(jiàn)的。

同時(shí),事務(wù)的一致性要求符合開(kāi)發(fā)人員定義的約束,如金額大于0、身高大于0等。

在上述的轉(zhuǎn)賬場(chǎng)景中,一致性能夠保證最終執(zhí)行完整個(gè)轉(zhuǎn)賬操作后,我賬戶(hù)的扣款金額與你賬戶(hù)到賬金額是一致的,同時(shí)如果我和你的賬戶(hù)余額不滿(mǎn)足金額的約束(如小于0),整個(gè)事務(wù)會(huì)回滾。

1.3 隔離性(Isolation)

事務(wù)的隔離性是指:在一次狀態(tài)轉(zhuǎn)換過(guò)程中不會(huì)受到其他狀態(tài)轉(zhuǎn)換的影響。

假設(shè)我和你都有100元,我發(fā)起兩次轉(zhuǎn)賬,轉(zhuǎn)賬金額都是50元,下面使用偽代碼來(lái)表示的操作步驟:

  1. 查詢(xún)我的賬戶(hù)余額 read my
  2. 我的賬戶(hù)扣款50元 my=my-50
  3. 50元開(kāi)始轉(zhuǎn)移
  4. 查詢(xún)你的賬戶(hù)余額 read yours
  5. 你的賬戶(hù)到賬50元 yours=yours+50

如果未保證隔離性就可能發(fā)生下面的情況:

時(shí)刻第一次轉(zhuǎn)賬第二次轉(zhuǎn)賬我的賬戶(hù)余額你的賬戶(hù)余額
1read my(100)
my=100yours=100
2
read my(100)my=100yours=100
3my=my-50=100-50=50
my=50yours=100
4read yours(100)my=my-50=100-50=50my=50yours=100
5yours=yours+50=100+50=150
my=50yours=150
6
read yours(150)my=50yours=150
7
yours=yours+50=150+50=200my=50yours=200
7endendmy=50yours=200

兩次轉(zhuǎn)賬后,最終的結(jié)果是我的賬戶(hù)余額為50元,你的賬戶(hù)余額為200元,這顯然是不對(duì)的。

而如果在保證事務(wù)隔離性的情況下,就不會(huì)發(fā)生上面的情況,損失的只是一定程度上的一致性。

1.4 持久性(Durability)

事務(wù)的持久性是指:事務(wù)在提交以后,它所做的修改就會(huì)被永久保存到數(shù)據(jù)庫(kù)。

在上述的轉(zhuǎn)賬場(chǎng)景中,持久性就保證了在轉(zhuǎn)賬成功之后,我的賬戶(hù)余額為0,你的賬戶(hù)余額為200。

2. 自動(dòng)提交與隱式提交

2.1 自動(dòng)提交

在 MySQL 中,我們可以通過(guò) begin 或 start transaction 來(lái)開(kāi)啟事務(wù),通過(guò) commit 來(lái)關(guān)閉事務(wù),如果 SQL 語(yǔ)句中沒(méi)有這兩個(gè)命令,默認(rèn)情況下每一條 SQL 都是一個(gè)獨(dú)立的事務(wù),在執(zhí)行完成后自動(dòng)提交。

比如:

update user set name='重塑' where id=1;復(fù)制代碼

假設(shè)我只執(zhí)行這一條更新語(yǔ)句,在我關(guān)閉 MySQL 客戶(hù)端然后重新打開(kāi)一個(gè)新的客戶(hù)端后,可以看到 user 表中的 name 字段值全變成了「重塑」,這也印證了這條更新語(yǔ)句在執(zhí)行后已被自動(dòng)提交。

自動(dòng)提交是 MySQL 的一個(gè)默認(rèn)屬性,可以通過(guò) SHOW VARIABLES LIKE 'autocommit' 語(yǔ)句來(lái)查看,當(dāng)它的值為 ON 時(shí),就代表開(kāi)啟事務(wù)的自動(dòng)提交。

mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+| Variable_name | Value |
+---------------+-------+| autocommit    | ON    |
+---------------+-------+1 row in set (0.00 sec)復(fù)制代碼

我們可以通過(guò) SET autocommit = OFF 來(lái)關(guān)閉事務(wù)的自動(dòng)提交。

2.2 隱式提交

然而,即便我們已經(jīng)將 autocommit 變量的值改為 OFF 關(guān)閉事務(wù)自動(dòng)提交了,在執(zhí)行某些 SQL 語(yǔ)句的時(shí)候,MySQL 還是會(huì)將事務(wù)自動(dòng)提交掉,這被稱(chēng)為隱式提交。

會(huì)觸發(fā)隱式提交的 SQL 語(yǔ)句有:

  • DDL(Data definition language,數(shù)據(jù)定義語(yǔ)言),如 create, drop, alter, truncate
  • 修改 MySQL 自帶表數(shù)據(jù)的語(yǔ)句,如 create/drop user, grant, set password
  • 在一個(gè)事務(wù)中,開(kāi)啟一個(gè)新的事務(wù),會(huì)隱式提交上一個(gè)事務(wù),如:
時(shí)刻事務(wù)A事務(wù)B
1begin;
2update user set name='重塑' where id=1;
3
select name from user where id=1;(N1)
4begin;
5
select name from user where id=1;(N2)

在事務(wù)B中有兩個(gè)查詢(xún)語(yǔ)句N(xiāo)1和N2,執(zhí)行的結(jié)果是N1=刺猬,N2=重塑,由此可以證明。

  • 其他還有一些管理語(yǔ)句就不一一舉例了,可自行百度。

3. 隔離級(jí)別

事務(wù)的隔離級(jí)別規(guī)定了一個(gè)事務(wù)中所做的修改,在事務(wù)內(nèi)和事務(wù)間的可見(jiàn)性。較低級(jí)別的隔離通常可以執(zhí)行更高的并發(fā),系統(tǒng)開(kāi)銷(xiāo)也更低。

在 SQL 標(biāo)準(zhǔn)中定義了四種事務(wù)的隔離級(jí)別,分別是讀未提交(Read Uncommitted)、讀已提交(Read Committed)、可重復(fù)讀(Repeatable Read)、可串行化(Serializable)。

為了詳細(xì)解釋這四種隔離級(jí)別及它們各自發(fā)生的現(xiàn)象,假設(shè)有兩個(gè)事務(wù)即將執(zhí)行,執(zhí)行內(nèi)容如下表:

時(shí)刻事務(wù)A事務(wù)B
1begin;
2
begin;
3
update user set name='重塑' where id=1;
4select name from user where id=1;(N1)
5
commit;
6select name from user where id=1;(N2)
7commit;
8select name from user where id=1;(N3)

在事務(wù)A和事務(wù)B執(zhí)行的過(guò)程中,有三處查詢(xún) N1,N2,N3,在每個(gè)隔離級(jí)別下,它們值的情況是不同的,下面分別討論。

3.1 讀未提交(Read Uncommitted)

在讀未提交的隔離級(jí)別下,事務(wù)中的修改,即便沒(méi)有提交,對(duì)其他事務(wù)也都是可見(jiàn)的。

在上述場(chǎng)景中,若數(shù)據(jù)庫(kù)的隔離級(jí)別為讀未提交,由于事務(wù)A可以讀取未提交事務(wù)B修改后的數(shù)據(jù),即時(shí)刻3中事務(wù)B的修改對(duì)事務(wù)A可見(jiàn),所以N1=重塑,N2=重塑,N3=重塑。

3.2 讀已提交(Read Committed)

在讀已提交的隔離級(jí)別下,事務(wù)中的修改只有在提交之后,才會(huì)對(duì)其他事務(wù)可見(jiàn)

在上述場(chǎng)景中,若數(shù)據(jù)庫(kù)的隔離級(jí)別為讀已提交,由于事務(wù)A只能讀取事務(wù)B提交后的數(shù)據(jù),即時(shí)刻3中事務(wù)B的修改對(duì)事務(wù)A不可見(jiàn),N2處的查詢(xún)?cè)谑聞?wù)B提交之后,故對(duì)事務(wù)A可見(jiàn)。所以N1=刺猬,N2=重塑,N3=重塑。

3.3 可重復(fù)讀(Repeatable Read)

可重復(fù)讀是 MySQL 的默認(rèn)事務(wù)隔離級(jí)別。在可重復(fù)讀的隔離級(jí)別下,一個(gè)事務(wù)中多次查詢(xún)相同的記錄,結(jié)果總是一致的。

在上述場(chǎng)景中,若數(shù)據(jù)庫(kù)的隔離級(jí)別為可重復(fù)讀,由于查詢(xún)N1和N2在一個(gè)事務(wù)中,所以它們的值都是「刺猬」,而N3是在事務(wù)A提交以后再進(jìn)行的查詢(xún),對(duì)事務(wù)B的修改是可見(jiàn)的,所以N3=重塑。

3.4 可串行化(Serializable)

在可串行化的隔離級(jí)別下,事務(wù)都是串行執(zhí)行的,讀會(huì)加讀鎖,寫(xiě)會(huì)加寫(xiě)鎖,事務(wù)不會(huì)并發(fā)執(zhí)行,所以也就不會(huì)發(fā)生異常情況。

在上述場(chǎng)景中,若數(shù)據(jù)庫(kù)的隔離級(jí)別為可串行化,首先開(kāi)啟事務(wù)A,在開(kāi)啟事務(wù)B時(shí)被阻塞,直到事務(wù)A提交之后才會(huì)開(kāi)啟事務(wù)B,所以N1=刺猬,N2=刺猬。而N3處的查詢(xún)會(huì)在事務(wù)B提交之后才執(zhí)行(事務(wù)B先被阻塞,執(zhí)行順序在N3查詢(xún)語(yǔ)句之前),所以N3=重塑。

4. 隔離級(jí)別導(dǎo)致的問(wèn)題

在不同的事務(wù)隔離級(jí)別中,如果遇到事務(wù)并發(fā)執(zhí)行,就會(huì)出現(xiàn)很多問(wèn)題,如臟讀(Dirty Read)、不可重復(fù)讀(Non-Repeatable Read)、幻讀(Phantom Read)等,下面就分別用不同的例子來(lái)詳細(xì)說(shuō)明這些問(wèn)題。

4.1 臟讀(Dirty Read)

臟讀(Dirty Read)是指一個(gè)事務(wù)可以讀取另一個(gè)未提交事務(wù)修改的數(shù)據(jù)。

看下面的案例,假設(shè)隔離級(jí)別為讀未提交:

時(shí)刻事務(wù)A事務(wù)B
1begin;
2
begin;
3
update user set name='重塑' where id=1;
4select name from user where id=1;(N1)
5
rollback;
6select name from user where id=1;(N2)
7commit;

在讀未提交的隔離級(jí)別下,N1的值是「重塑」,由于事務(wù)B的回滾,N2的值是「刺猬」。這里在N1處就發(fā)生了臟讀,顯然N1處的查詢(xún)結(jié)果是一個(gè)臟數(shù)據(jù),會(huì)對(duì)正常業(yè)務(wù)產(chǎn)生影響。

臟讀會(huì)發(fā)生在讀未提交的隔離級(jí)別中。

4.2 不可重復(fù)讀(Non-Repeatable Read)

不可重復(fù)讀(Non-Repeatable Read)是指,兩次執(zhí)行相同的查詢(xún)可能會(huì)得到不一樣的結(jié)果。

繼續(xù)使用介紹隔離級(jí)別時(shí)的AB事務(wù)案例,同時(shí)假設(shè)隔離級(jí)別為讀已提交:

時(shí)刻事務(wù)A事務(wù)B
1begin;
2
begin;
3
update user set name='重塑' where id=1;
4select name from user where id=1;(N1)
5
commit;
6select name from user where id=1;(N2)
7commit;
8select name from user where id=1;(N3)

在讀已提交的隔離級(jí)別下,事務(wù)可以讀取到其他事務(wù)提交的數(shù)據(jù)。在上述案例中結(jié)果是N1=刺猬,N2=重塑,N3=重塑,在事務(wù)A中,有兩次相同的查詢(xún)N1和N2,但是這兩次查詢(xún)的結(jié)果并不相同,這就發(fā)生了不可重復(fù)讀。

不可重復(fù)讀會(huì)發(fā)生在讀未提交、讀已提交的隔離級(jí)別中。

4.3 幻讀(Phantom Read)

幻讀(Phantom Read)是指,一個(gè)事務(wù)在讀取某個(gè)范圍內(nèi)記錄時(shí),另外一個(gè)事務(wù)在該范圍內(nèi)插入一條新記錄,當(dāng)之前的事務(wù)再次讀取這個(gè)范圍的記錄時(shí),會(huì)讀到這條新記錄。

看下面的案例,假設(shè)此時(shí)隔離級(jí)別為可重復(fù)讀:

時(shí)刻事務(wù)A事務(wù)B
1begin;
2select name from user;(N1)
3
begin;
4
insert into user values(2, '五條人');
5
commit;
6select name from user;(N2)
7select name from user for update;(N3)
8commit;

事務(wù)A有三次查詢(xún),在N1和N2之間,事務(wù)B執(zhí)行了一條 insert語(yǔ)句并提交,N3處的查詢(xún)使用的是 for update

N1處的結(jié)果很顯然只有「刺猬」,N2處的結(jié)果由于事務(wù)A開(kāi)啟在事務(wù)B之前,所以也是「刺猬」,而N3處的結(jié)果理論上在可重復(fù)讀的隔離級(jí)別中也應(yīng)該只有「刺猬」,但實(shí)際上N2的結(jié)果是「刺猬」和「五條人」,這就發(fā)生了幻讀。

這就很奇怪了,不是說(shuō)可重復(fù)讀的隔離級(jí)別能夠保證一個(gè)事務(wù)中多次查詢(xún)相同的記錄,結(jié)果總是一致的嗎?這種結(jié)果并不滿(mǎn)足可重復(fù)讀的定義。

事實(shí)上,在可重復(fù)讀的隔離級(jí)別下,如果使用的是當(dāng)前讀,那么就可能發(fā)生幻讀現(xiàn)象。

當(dāng)前讀和快照讀會(huì)在下文中介紹事務(wù)的實(shí)現(xiàn)原理及 MVCC 時(shí)討論,這里先給一個(gè)結(jié)論。

幻讀會(huì)發(fā)生在讀未提交、讀已提交、可重復(fù)讀的隔離級(jí)別中。

這里需要額外注意的是:幻讀和不可重復(fù)讀都是說(shuō)在一個(gè)事務(wù)中的同一個(gè)查詢(xún)語(yǔ)句結(jié)果不同,但幻讀更側(cè)重于查詢(xún)到其他事務(wù)新插入的數(shù)據(jù)(insert)或其他事務(wù)刪除的數(shù)據(jù)(delete),而不可重復(fù)讀的范圍更廣,只要結(jié)果不同就可以認(rèn)為是不可重復(fù)讀,但一般我們認(rèn)為不可重復(fù)讀更側(cè)重于其他事務(wù)對(duì)數(shù)據(jù)的更新(update)。

4.4 小結(jié)

通過(guò)上面的描述,我們已經(jīng)知道四種隔離級(jí)別的概念以及它們分別會(huì)遇到的問(wèn)題,事務(wù)的隔離級(jí)別越高,隔離性就越強(qiáng),所遇到的問(wèn)題也就越少。但同時(shí),隔離級(jí)別越高,并發(fā)能力就越弱。

下表是對(duì)隔離級(jí)別的概念不同隔離級(jí)別會(huì)發(fā)生的問(wèn)題情況的小結(jié):

隔離級(jí)別臟讀不可重復(fù)讀幻讀概念
讀已提交事務(wù)中的修改,即便沒(méi)有提交,對(duì)其他事務(wù)也都是可見(jiàn)的
讀未提交
事務(wù)中的修改只有在提交之后,才會(huì)對(duì)其他事務(wù)可見(jiàn)
可重復(fù)讀

一個(gè)事務(wù)中多次查詢(xún)相同的記錄,結(jié)果總是一致的
可串行化


事務(wù)都是串行執(zhí)行的,讀會(huì)加讀鎖,寫(xiě)會(huì)加寫(xiě)鎖

5. MVCC

MVCC(Multi-Version Concurrency Control)即多版本并發(fā)控制,這是 MySQL 為了提高數(shù)據(jù)庫(kù)并發(fā)性能而實(shí)現(xiàn)的。它可以在并發(fā)讀寫(xiě)數(shù)據(jù)庫(kù)時(shí),保證不同事務(wù)的讀-寫(xiě)操作并發(fā)執(zhí)行,同時(shí)也能解決臟讀、不可重復(fù)讀、幻讀等事務(wù)隔離問(wèn)題。

在前文討論幻讀的時(shí)候提到過(guò)當(dāng)前讀的概念,正是由于當(dāng)前讀,才會(huì)在可重復(fù)讀的隔離級(jí)別下也會(huì)發(fā)生幻讀的情況。

在解釋可重復(fù)讀隔離級(jí)別下發(fā)生幻讀的原因之前,首先介紹 MVCC 的實(shí)現(xiàn)原理。

5.1 MVCC 的實(shí)現(xiàn)原理

首先我們需要知道,InnoDB 的數(shù)據(jù)頁(yè)中每一行的數(shù)據(jù)是有隱藏字段的:

  • DB_ROW_ID: 隱式主鍵,若表結(jié)構(gòu)中未定義主鍵,InnoDB 會(huì)自動(dòng)生成該字段作為表的主鍵
  • DB_TRX_ID: 事務(wù)ID,代表修改此行記錄的最后一次事務(wù)ID
  • DB_ROLL_PTR: 回滾指針,指向此行記錄的上一個(gè)版本(上一個(gè)事務(wù)ID對(duì)應(yīng)的記錄)

每一條修改語(yǔ)句都會(huì)相應(yīng)地記錄一條回滾語(yǔ)句(undo log),如果把每一條回滾語(yǔ)句視為一條數(shù)據(jù)表中的記錄,那么通過(guò)事務(wù)ID和回滾指針就可以將對(duì)同一行的修改記錄看作一個(gè)鏈表,鏈表上的每一個(gè)節(jié)點(diǎn)就是一個(gè)快照版本,這就是 MVCC 中多版本的意思。

舉個(gè)例子,假設(shè)對(duì) user 表中唯一的一行「刺猬」進(jìn)行多次修改。

update user set name='重塑' where id=1;update user set name='木馬' where id=1;update user set name='達(dá)達(dá)' where id=1;復(fù)制代碼

那么這條記錄的版本鏈就是:

MySQL事務(wù)、隔離級(jí)別及MVCC是什么

在這個(gè)版本鏈中,頭結(jié)點(diǎn)就是當(dāng)前記錄的最新版本。DB_TRX_ID 事務(wù)ID 字段是非常重要的屬性,先 Mark 一下。

除此之外,在讀已提交(RC,Read Committed)和可重復(fù)讀(RR,Repeatable Read)的隔離級(jí)別中,事務(wù)在啟動(dòng)的時(shí)候會(huì)創(chuàng)建一個(gè)讀視圖(Read View),用它來(lái)記錄當(dāng)前系統(tǒng)的活躍事務(wù)信息,通過(guò)讀視圖來(lái)進(jìn)行本事務(wù)之間的可見(jiàn)性判斷

在讀視圖中有兩個(gè)重要的屬性:

  • 當(dāng)前事務(wù)ID:表示生成讀視圖的事務(wù)的事務(wù)ID
  • 事務(wù)ID列表:表示在生成讀視圖時(shí),當(dāng)前系統(tǒng)中活躍著的事務(wù)ID列表
  • 最小事務(wù)ID:表示在生成讀視圖時(shí),當(dāng)前系統(tǒng)中活躍著的最小事務(wù)ID
  • 下一個(gè)事務(wù)ID:表示在生成讀視圖時(shí),系統(tǒng)應(yīng)該分配給下一個(gè)事務(wù)的事務(wù)ID

需要注意下一個(gè)事務(wù)I的值,并不是事務(wù)ID列表中的最大值+1,而是當(dāng)前系統(tǒng)中已存在過(guò)的事務(wù)的最大值+1。例如當(dāng)前數(shù)據(jù)庫(kù)中活躍的事務(wù)有(1,2),此時(shí)事務(wù)2提交,同時(shí)又開(kāi)啟了新事務(wù),在生成的讀視圖中,下一個(gè)事務(wù)ID的值為3。

我們通過(guò)將版本鏈與讀視圖兩者結(jié)合起來(lái),來(lái)進(jìn)行并發(fā)事務(wù)間可見(jiàn)性的判斷,判斷規(guī)則如下(假設(shè)現(xiàn)在要判斷事務(wù)A是否可以訪問(wèn)到事務(wù)B的修改記錄):

  • 若事務(wù)B的當(dāng)前事務(wù)ID小于事務(wù)A的最小事務(wù)ID的值,代表事務(wù)B是在事務(wù)A生成讀視圖之前就已經(jīng)提交了的,所以事務(wù)B對(duì)于事務(wù)A來(lái)說(shuō)是可見(jiàn)的。
  • 若事務(wù)B的當(dāng)前事務(wù)ID大于或等于事務(wù)A下一個(gè)事務(wù)ID的值,代表事務(wù)B是在事務(wù)A生成讀視圖之后才開(kāi)啟,所以事務(wù)B對(duì)于事務(wù)A來(lái)說(shuō)是不可見(jiàn)的。
  • 若事務(wù)B的當(dāng)前事務(wù)ID在事務(wù)A的最小事務(wù)ID下一個(gè)事務(wù)ID之間(左閉右開(kāi),[最小事務(wù)ID, 下一個(gè)事務(wù)ID)),需要分兩種情況討論:
    • 若事務(wù)B的當(dāng)前事務(wù)ID在事務(wù)A的事務(wù)ID列表中,代表創(chuàng)建事務(wù)A時(shí)事務(wù)B還是活躍的,未提交,所以事務(wù)B對(duì)于事務(wù)A來(lái)說(shuō)是不可見(jiàn)的。
    • 若事務(wù)B的當(dāng)前事務(wù)ID不在事務(wù)A的事務(wù)ID列表中,代表創(chuàng)建事務(wù)A時(shí)事務(wù)B已經(jīng)提交,所以事務(wù)B對(duì)于事務(wù)A來(lái)說(shuō)是可見(jiàn)的。

如果事務(wù)B對(duì)于事務(wù)A來(lái)說(shuō)是不可見(jiàn)的,就需要順著修改記錄的版本鏈,從回滾指針開(kāi)始往前遍歷,直到找到第一個(gè)對(duì)于事務(wù)A來(lái)說(shuō)是可見(jiàn)的事務(wù)ID,或者遍歷完版本鏈也未找到(表示這條記錄對(duì)事務(wù)A不可見(jiàn))。

這就是 MVCC 的實(shí)現(xiàn)原理。

5.2 讀視圖的創(chuàng)建時(shí)機(jī)

這里需要注意的是讀視圖的創(chuàng)建時(shí)機(jī),在上面的論述中我們已經(jīng)知道事務(wù)在啟動(dòng)時(shí)會(huì)創(chuàng)建一個(gè)讀視圖(Read View),而開(kāi)啟一個(gè)事務(wù)有兩種方式,一是 begin/start transaction,二是start transaction with consistent snapshot,通過(guò)這兩種方式開(kāi)啟事務(wù),創(chuàng)建讀視圖的時(shí)機(jī)也是不同的:

  • 如果是以 begin/start transaction 方式開(kāi)啟事務(wù),讀視圖會(huì)在執(zhí)行第一個(gè)快照讀語(yǔ)句時(shí)創(chuàng)建
  • 如果以 start transaction with consistent snapshot 方式開(kāi)啟事務(wù),同時(shí)便會(huì)創(chuàng)建讀視圖

5.3 MVCC 的運(yùn)行過(guò)程

為了詳細(xì)說(shuō)明 MVCC 的運(yùn)行過(guò)程,下面舉個(gè)例子,假設(shè)當(dāng)前存在有兩個(gè)事務(wù)(事務(wù)隔離級(jí)別為 MySQL 默認(rèn)的可重復(fù)讀):

這里需要注意的是事務(wù)的啟動(dòng)時(shí)機(jī),在上面的論述中我們已經(jīng)知道事務(wù)在啟動(dòng)時(shí)會(huì)創(chuàng)建一個(gè)讀視圖(Read View),而開(kāi)啟一個(gè)事務(wù)有兩種方式,一是 begin/start transaction,二是start transaction with consistent snapshot,通過(guò)這兩種方式開(kāi)啟事務(wù),創(chuàng)建讀視圖的時(shí)機(jī)也是不同的:

  • 如果是以 begin/start transaction 方式開(kāi)啟事務(wù),讀視圖會(huì)在執(zhí)行第一個(gè)快照讀語(yǔ)句時(shí)創(chuàng)建
  • 如果以 start transaction with consistent snapshot 方式開(kāi)啟事務(wù),同時(shí)便會(huì)創(chuàng)建讀視圖
時(shí)刻事務(wù)A事務(wù)B
1start transaction with consistent snapshot;
2
start transaction with consistent snapshot;
3
update user set name='重塑' where id=1;
4select name from user where id=1;(N1)
5
commit;
6select name from user where id=1;(N2)
7commit;

然后根據(jù)上面所描述的版本鏈以及兩個(gè)事務(wù)開(kāi)啟時(shí)的讀視圖來(lái)分析 MVCC 的運(yùn)行過(guò)程。

MySQL事務(wù)、隔離級(jí)別及MVCC是什么

上圖是兩個(gè)事務(wù)開(kāi)啟時(shí)的讀視圖,而當(dāng)事務(wù)B的更新語(yǔ)句執(zhí)行之后,id=1行的版本鏈如下所示。

MySQL事務(wù)、隔離級(jí)別及MVCC是什么

先來(lái)看N1處的查詢(xún)語(yǔ)句,事務(wù)B的當(dāng)前事務(wù)ID=2,其值等于事務(wù)A的下一個(gè)事務(wù)ID,所以按照上文中所論述的可見(jiàn)性判斷,事務(wù)B對(duì)于事務(wù)A來(lái)說(shuō)是不可見(jiàn)的,需要循著當(dāng)前行的版本鏈網(wǎng)上檢索。

于是循著版本鏈來(lái)到DB_TRX_ID=1事務(wù)ID=1的歷史版本,恰巧等于事務(wù)A的事務(wù)ID值,也就是事務(wù)A開(kāi)啟時(shí)該行的版本,此版本對(duì)于事務(wù)A來(lái)說(shuō)當(dāng)然是可見(jiàn)的,所以讀取到了id=1行的name='刺猬',即最終N1=刺猬。

再來(lái)看N2處的查詢(xún)語(yǔ)句,此時(shí)事務(wù)B已提交,版本鏈還是如上圖所示,由于當(dāng)前版本的事務(wù)ID等于事務(wù)A讀視圖中的下一個(gè)事務(wù)ID,所以當(dāng)前版本的記錄對(duì)于事務(wù)A來(lái)說(shuō)是不可見(jiàn)的,所以同樣N2=刺猬。

這里需要注意的是,若例子中事務(wù)A的時(shí)刻4語(yǔ)句變更為對(duì)該行的更新語(yǔ)句,那么事務(wù)A便會(huì)等待事務(wù)B提交之后再執(zhí)行更新語(yǔ)句,這是因?yàn)槭聞?wù)B未提交,即事務(wù)B對(duì)于id=1行的寫(xiě)鎖未釋放,而事務(wù)A也要更新該行,必須是更新當(dāng)前的最新版本(當(dāng)前讀)才可以,所以事務(wù)A就被阻塞了,必須等待事務(wù)B對(duì)該行的寫(xiě)鎖釋放,才會(huì)繼續(xù)執(zhí)行更新語(yǔ)句。

5.4 RC 與 RR 生成讀視圖的時(shí)機(jī)對(duì)比

上面所討論的 MVCC 運(yùn)行過(guò)程都是針對(duì)可重復(fù)讀(RR, Repeatable Read)隔離級(jí)別的,如果是讀已提交(RC, Read Committed)級(jí)別呢?

上文中已經(jīng)討論過(guò)讀已提交隔離級(jí)別中關(guān)于不可重復(fù)讀的情況了,這里就不再舉例,直接給出結(jié)論就可以了。

  • 可重復(fù)讀(RR, Repeatable Read)隔離級(jí)別下生成讀視圖(Read View)的時(shí)機(jī)是開(kāi)啟事務(wù)的時(shí)候
  • 讀已提交(RC, Read Committed)隔離級(jí)別下生成讀視圖(Read View)的時(shí)機(jī)是每一條語(yǔ)句執(zhí)行前

對(duì)于上文中描述 MVCC 執(zhí)行過(guò)程中的例子,如果隔離級(jí)別是讀已提交(RC, Read Committed):

  • N1處的查詢(xún)語(yǔ)句,由于事務(wù)B還未提交,事務(wù)A可見(jiàn)的版本依舊是事務(wù)ID=1的版本,所以N1=刺猬
  • N2處的查詢(xún)語(yǔ)句,事務(wù)B已提交,N2處查詢(xún)語(yǔ)句執(zhí)行時(shí)也會(huì)生成讀視圖,其當(dāng)前事務(wù)ID=3,而在該記錄的版本鏈中,當(dāng)前版本的事務(wù)ID DB_TRX_ID=2,在N2查詢(xún)語(yǔ)句事務(wù)ID之前,是可見(jiàn)的,所以N2=重塑

5.5 當(dāng)前讀與快照讀

  • 當(dāng)前讀:讀取記錄的最新版本
  • 快照讀:讀取記錄時(shí)會(huì)根據(jù)一定規(guī)則讀取事務(wù)可見(jiàn)版本的記錄

5.6 可重復(fù)讀發(fā)生幻讀的原因

在理解了 MVCC 之后,我們?cè)賮?lái)看在可重復(fù)讀隔離級(jí)別下發(fā)生幻讀的原因。上文中說(shuō)到正是由于當(dāng)前讀,才會(huì)在可重復(fù)讀的隔離級(jí)別下發(fā)生幻讀的情況,首先來(lái)回顧一下例子。

時(shí)刻事務(wù)A事務(wù)B
1begin;
2select name from user;(N1)
3
begin;
4
insert into user values(2, '五條人');
5
commit;
6select name from user;(N2)
7select name from user for update;(N3)
8commit;

N1,N2處的查詢(xún)想必已經(jīng)十分明確都是「刺猬」了。而在N3處所使用的查詢(xún)語(yǔ)句是for update,使用它進(jìn)行查詢(xún)就會(huì)對(duì)目標(biāo)記錄添加一把「行級(jí)鎖」,行級(jí)鎖的意義以后再說(shuō),現(xiàn)在只需要知道for update能夠鎖住目標(biāo)記錄就可以了。

加鎖自然是防止別人修改,那么理所當(dāng)然,鎖住的當(dāng)然也就是記錄的最新版本了。所以,在使用for update進(jìn)行查詢(xún)的時(shí)候,會(huì)使用當(dāng)前讀,讀到目標(biāo)記錄的最新版本,所以在N3處的查詢(xún)語(yǔ)句就會(huì)把事務(wù)B中本對(duì)于事務(wù)A來(lái)說(shuō)不可見(jiàn)的記錄也查詢(xún)出來(lái),也就發(fā)生了幻讀。

使用當(dāng)前讀的語(yǔ)句有:

  • select ... for update
  • select ... lock in share mode(共享讀鎖)
  • update ...
  • insert ...
  • delete ...

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享MySQL事務(wù)、隔離級(jí)別及MVCC是什么內(nèi)容對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,遇到問(wèn)題就找億速云,詳細(xì)的解決方法等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI