自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入解讀 MySQL 的 MVCC 與事務(wù)隔離級(jí)別

數(shù)據(jù)庫(kù) MySQL
本文我們將一同踏上這場(chǎng)解析 MySQL MVCC 和事務(wù)隔離級(jí)別的精彩之旅,逐步揭開(kāi)它們神秘的面紗,探尋其背后蘊(yùn)含的深刻原理和實(shí)際應(yīng)用價(jià)值。

在當(dāng)今的數(shù)據(jù)庫(kù)世界中,MySQL 以其強(qiáng)大的功能和廣泛的應(yīng)用備受矚目。而其中的 MVCC(多版本并發(fā)控制)和事務(wù)隔離級(jí)別更是關(guān)鍵且核心的概念,它們猶如數(shù)據(jù)庫(kù)運(yùn)行的精密齒輪,協(xié)同作用確保著數(shù)據(jù)的完整性、一致性和高效的并發(fā)處理。

當(dāng)我們深入探究 MySQL 的內(nèi)部機(jī)制時(shí),MVCC 展現(xiàn)出其獨(dú)特的魅力,它巧妙地解決了并發(fā)操作中可能產(chǎn)生的諸多問(wèn)題。與此同時(shí),事務(wù)隔離級(jí)別則為不同場(chǎng)景下的數(shù)據(jù)處理提供了靈活而精準(zhǔn)的規(guī)則框架。理解這兩者,不僅是對(duì) MySQL 技術(shù)精髓的把握,更是開(kāi)啟高效數(shù)據(jù)庫(kù)應(yīng)用和系統(tǒng)開(kāi)發(fā)的關(guān)鍵鑰匙。在接下來(lái)的篇章中,我們將一同踏上這場(chǎng)解析 MySQL MVCC 和事務(wù)隔離級(jí)別的精彩之旅,逐步揭開(kāi)它們神秘的面紗,探尋其背后蘊(yùn)含的深刻原理和實(shí)際應(yīng)用價(jià)值。

一、詳解事務(wù)的基本概念

1. 什么是事務(wù)

現(xiàn)在我們開(kāi)發(fā)的一個(gè)功能需要進(jìn)行操作多張表,假如我們遇到以下幾種情況:

  • 某個(gè)邏輯報(bào)錯(cuò)
  • 數(shù)據(jù)庫(kù)連接中斷
  • 某臺(tái)服務(wù)器突然宕機(jī)
  • .......

這時(shí)候我們數(shù)據(jù)庫(kù)執(zhí)行的操作可能才到一半,所以為了避免這種一半一半的情況,我們就需要事務(wù)來(lái)保證數(shù)據(jù)一致性。 所以事務(wù)就是當(dāng)作一個(gè)原子的邏輯組操作,要么全都成功執(zhí)行,要么全部都失敗。事務(wù)有分分布式事務(wù)和數(shù)據(jù)庫(kù)事務(wù),如果沒(méi)有特指,我們平時(shí)所說(shuō)的事務(wù)都是數(shù)據(jù)庫(kù)事務(wù),也就是本文探討的話(huà)題。

2. 事務(wù)的四大特性

  • 原子性(Atomicity):一組操作要構(gòu)成一個(gè)原子,原子可以看作事務(wù)的最小單位,不可在進(jìn)行分割了,要么都執(zhí)行,要么都不執(zhí)行。
  • 一致性(Consistency):經(jīng)過(guò)一個(gè)事務(wù)的操作后,前后要保持?jǐn)?shù)據(jù)一致性,例如我們要用數(shù)據(jù)庫(kù)記錄一次轉(zhuǎn)賬操作,那么兩個(gè)數(shù)據(jù)經(jīng)過(guò)轉(zhuǎn)賬邏輯之后總額還是保持不變。
  • 隔離性(Isolation):在并發(fā)場(chǎng)景下,每個(gè)事務(wù)之間的操作互不干擾。
  • 持久性(Durability):存儲(chǔ)到數(shù)據(jù)庫(kù)中的數(shù)據(jù)永不丟失,及時(shí)數(shù)據(jù)庫(kù)發(fā)生故障,當(dāng)然機(jī)器被破壞了那就另說(shuō)了。

3. 并發(fā)事務(wù)帶來(lái)那些問(wèn)題

這里筆者先說(shuō)一個(gè)概念,具體會(huì)在后文示例中詳盡介紹。

臟讀:我們舉個(gè)例子:

  • 我們開(kāi)啟一個(gè)事務(wù)A,準(zhǔn)備讀取user表的數(shù)據(jù)。
  • 此時(shí),事務(wù)B將事務(wù)A要讀取的數(shù)據(jù)修改了,但事務(wù)還沒(méi)提交.
  • A卻能看到這個(gè)未提交的結(jié)果即sex為1(而且這個(gè)結(jié)果后續(xù)還不一定提交)。

這種其他事務(wù)還沒(méi)提交的結(jié)果能被另一個(gè)事務(wù)看到的情況就屬于臟讀。

幻讀:我們?cè)倥e個(gè)例子:

  • 事務(wù)A查詢(xún)user表,此時(shí)表中有10條數(shù)據(jù)。
  • 在此期間,事務(wù)B插入5條數(shù)據(jù)。
  • 事務(wù)A再次查發(fā)現(xiàn)有15條事務(wù)。

這種同一次事務(wù)兩次查詢(xún)結(jié)果不一致的情況是幻讀:

不可重復(fù)讀,仍然舉一個(gè)例子:

  • 事務(wù)A讀取id為1的數(shù)據(jù),name為xiaoming。
  • 事務(wù)B在此期間更新id為1的數(shù)據(jù)并提交這個(gè)事務(wù)
  • 結(jié)果事務(wù)A再次讀取時(shí)發(fā)現(xiàn)name變了。 這就是不可重復(fù)讀。

你可能會(huì)問(wèn)了,這和幻讀聽(tīng)起來(lái)是一個(gè)概念啊,他倆有什么區(qū)別? 幻讀說(shuō)是針對(duì)插入或者刪除操作后導(dǎo)致數(shù)據(jù)前后不一致的情況,而不可重復(fù)讀是針對(duì)兩次相同查詢(xún)操作出現(xiàn)數(shù)據(jù)不一致。

數(shù)據(jù)丟失:這個(gè)就很好理解了,高并發(fā)場(chǎng)景下,事務(wù)A修改id為1的money+100,事務(wù)B修改id為1的money+200,他們統(tǒng)一時(shí)間讀取,先后寫(xiě)入,這就導(dǎo)致如果事務(wù)A后寫(xiě)入,那么money最后只加了100,如果事務(wù)B后寫(xiě)入,那么money就少了100。

二、詳解事務(wù)的隔離級(jí)別

1. 讀未提交(READ UNCOMMITTED)

在這個(gè)級(jí)別下,任何事務(wù)的修改操作即使沒(méi)有提交,其他事務(wù)也能看到,造成我們上述所說(shuō)的臟讀,對(duì)此我們不妨用下面這段SQL來(lái)驗(yàn)證一下:

首先我們先建個(gè)測(cè)試表:

create table test2 (id int,name varchar(10),money int); 
insert into test2 values(1,'xiaoming',100);
insert into test2 values(2,'xiaowang',100);

事務(wù)A開(kāi)啟事務(wù),進(jìn)行test2  的更新操作,不提交

start transaction;
-- 小明+100元
update test2   set money = money +100 where name ='xiaoming';
-- 小王減100元
update test2   set money =money -100 where name ='xiaowang';

事務(wù)B設(shè)置為讀未提交的隔離級(jí)別:

SET SESSION TRANSACTION ISOLATION LEVEL READ committed;
select * from test2 t ;

查詢(xún)結(jié)果是事務(wù)B看到了事務(wù)A的更新操作,造成臟讀。

對(duì)應(yīng)結(jié)果如下:

id|name    |money|
--+--------+-----+
 1|xiaoming|  200|
 2|xiaowang|    0|

同理這個(gè)讀未提交,也會(huì)造成:

  • 幻讀(同一個(gè)事務(wù)同一次查詢(xún)記錄數(shù)不一樣)
  • 不可重復(fù)讀(同一個(gè)事務(wù)下查詢(xún)記錄的值不一樣)

2. 讀已提交(READ COMMITTED)

這個(gè)概念也很好理解,每個(gè)事務(wù)只能看到其他事務(wù)提交后的數(shù)據(jù)。避免了臟讀,但是無(wú)法避免幻讀和不可重復(fù)讀。 我們就以幻讀為例,如下圖,事務(wù)B首先查詢(xún)到數(shù)據(jù)表中沒(méi)有id為1的用戶(hù),在這個(gè)查詢(xún)結(jié)束后,事務(wù)A進(jìn)行一次插入操作但是事務(wù)還未提交。

然后事務(wù)A將數(shù)據(jù)提交,事務(wù)B再次查詢(xún)就發(fā)現(xiàn)了數(shù)據(jù),出現(xiàn)幻讀:

了解流程之后,我們拿SQL印證一下,首先創(chuàng)建數(shù)據(jù)表:

drop table if exists account1;


CREATE TABLE `account1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `account1_un` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=UTF8MB4;

事務(wù)B查詢(xún),沒(méi)數(shù)據(jù):

SET SESSION TRANSACTION ISOLATION LEVEL READ committed;
START TRANSACTION;

-- 查詢(xún)表,此時(shí)沒(méi)有數(shù)據(jù)
SELECT * from account1;

事務(wù)A在此期間插入,事務(wù)不提交:

SET SESSION TRANSACTION ISOLATION LEVEL READ committed;
START TRANSACTION;
-- 在上一個(gè)事務(wù)查詢(xún)后,插入一條事務(wù)但是不提交
insert into account1(id,name,balance) values(1,'zhangsan',1000);

此時(shí)事務(wù)B還是沒(méi)看到數(shù)據(jù),然后我們將上述的事務(wù)A數(shù)據(jù)commit,事務(wù)B看到這條數(shù)據(jù)出現(xiàn)幻讀:

3. 可重復(fù)讀(REPEATABLE READ)

這個(gè)隔離級(jí)別,也很好理解,同一個(gè)事務(wù)內(nèi),多次查詢(xún)的數(shù)據(jù)都是一樣的。我們不妨基于上面的例子實(shí)驗(yàn)一下

首先事務(wù)B查詢(xún),沒(méi)有任何數(shù)據(jù):

SET SESSION TRANSACTION ISOLATION LEVEL  REPEATABLE READ;
START TRANSACTION;
select * from account1 a  where id=3;

此時(shí)xiaoming的數(shù)據(jù)為300:

id|name    |balance|
--+--------+-------+
 3|xiaoming|    100|

事務(wù)A執(zhí)行更新并提交:

SET SESSION TRANSACTION ISOLATION LEVEL  REPEATABLE READ;
START TRANSACTION;
update account1 set balance=0 where id=3;
commit;

事務(wù)B再查數(shù)據(jù)還是不變,還是300:

id|name    |balance|
--+--------+-------+
 3|xiaoming|    100|

總的來(lái)說(shuō)可重復(fù)讀避免了臟讀和不可重復(fù)讀,但是幻讀還是無(wú)法避免:

4. 串行化(SERIALIZABLE)

事務(wù)隔離最高級(jí)別,通過(guò)鎖的方式控制并發(fā)流程,解決上述一切問(wèn)題。

三、詳解多版本并發(fā)控制MVCC

1. 當(dāng)前讀和快照讀

快照讀:即讀取數(shù)據(jù)是從快照中獲取的,事務(wù)在進(jìn)行事務(wù)讀取時(shí)不上鎖,這就是mysql并發(fā)讀寫(xiě)性能高的原因之一。 而當(dāng)前讀反之,讀取數(shù)據(jù)時(shí)會(huì)上鎖,這也就意味著即使你的隔離級(jí)別是可重復(fù)讀,你用當(dāng)前讀也能讀取到其他事務(wù)的最新結(jié)果,造成不可重復(fù)讀。

我們舉個(gè)例子,首先事務(wù)A讀取數(shù)據(jù),假設(shè)數(shù)據(jù)值是100:

begin;
-- 讀取到a的money為100
select * from account1 a ;

事務(wù)B更新事務(wù)并提交:

update account1 set money=1000 where id=1;

事務(wù)A使用快照讀,數(shù)據(jù)還是100:

select * from account1 a ; --快照讀 舊數(shù)據(jù)

一旦使用當(dāng)前讀,就是其他事務(wù)提交的新數(shù)據(jù)了:

--兩個(gè)都是當(dāng)前讀,得到最新結(jié)果
select * from account1 a for update; 
select * from account1 a lock in share mode;

2. undo.log概念掃盲

首先說(shuō)說(shuō)undo log,在innoDB的聚簇索引中,每一條記錄除了我們表中的數(shù)據(jù)以外,還會(huì)額外記錄名為事務(wù)id(transaction id)的隱藏列。每當(dāng)用戶(hù)對(duì)當(dāng)前數(shù)據(jù)進(jìn)行修改操作后,新值的數(shù)據(jù)的事務(wù)id就會(huì)遞增。 同時(shí)每行數(shù)據(jù)還有一個(gè)回滾指針(roll_pointer),如下圖所示,每當(dāng)用戶(hù)對(duì)索引進(jìn)行更新之后,舊的數(shù)據(jù)就會(huì)被存放到undo log中,新的數(shù)據(jù)的回滾指針指向這條最新的舊數(shù)據(jù)(就是剛剛存到undo log中的數(shù)據(jù),通俗的說(shuō)是最新的垃圾),用于后續(xù)可能需要的回滾操作:

3. readView概念掃盲

接下來(lái)就說(shuō)說(shuō)readView,readView就是真正用到undo log的東西,如下圖所示,它由三個(gè)部分組成,分別是:

  • 已提交事務(wù):已提交事務(wù)中記錄的則是已經(jīng)被提交的事務(wù)id集合。
  • 活躍事務(wù):這個(gè)則記錄那些還能活動(dòng)且還沒(méi)被提交的事務(wù),其中min_trx_id指向活躍事務(wù)的最小值。
  • 未開(kāi)始事務(wù):這里面則是存放待使用的事務(wù)id值,其中max_trx_id就是記錄這一塊的最小值。

4. 基于可重復(fù)讀版本理解SQL的MVCC工作機(jī)制

了解了undo.log和readView,我們就可以了解mvcc的工作機(jī)制了。就先以可重復(fù)讀RR為例,我們來(lái)了解一下如何結(jié)合undo.log和readView實(shí)現(xiàn)可重復(fù)讀的。

可重復(fù)讀這個(gè)級(jí)別的readView只會(huì)在事務(wù)剛剛開(kāi)始時(shí)創(chuàng)建,這也就意味著后續(xù)數(shù)據(jù)無(wú)論怎么變化,readView都以第一次創(chuàng)建的為主:

假設(shè)我們現(xiàn)在account表數(shù)據(jù)存在一條id為1的數(shù)據(jù)xiaoming,然后事務(wù)trx_id為100的事務(wù)基于RR級(jí)別將name先更新為xiaoming_50然后再更新為xiaoming_100,但是事務(wù)還沒(méi)提交,此時(shí)對(duì)應(yīng)的版本鏈如下所示:

需要注意的是,只有進(jìn)行SQL修改操作即insert、update、delete才會(huì)分配一個(gè)事務(wù)id,所以我們本在進(jìn)行查詢(xún)之前執(zhí)行一些無(wú)關(guān)緊要的update操作,生成一個(gè)事務(wù)200開(kāi)始查詢(xún)執(zhí)行下面這條sql查詢(xún),即查詢(xún)id為1的數(shù)據(jù):

-- 執(zhí)行一些無(wú)關(guān)緊要的update
select * from account1 a where id=1;

然后事務(wù)啟動(dòng)創(chuàng)建readView,結(jié)合版本鏈記錄來(lái)看,活躍但是未提交事務(wù)值為100,即min_trx_id為100,而我們的事務(wù)為200,這也就意味著max_trx_id為201,由此可得活躍的讀寫(xiě)事務(wù)m_ids列表有100、200之間。

所以事務(wù)200生成readView如下,然后順著版本鏈開(kāi)始獲取數(shù)據(jù)首先看到xiaoming_100事務(wù)id為100處于活躍事務(wù)列表不符合要求繼續(xù)順著指針往下走,看到xiaoming_50也不符合要求,繼續(xù)順著指針往下走,看到xiaoming事務(wù)id值為80小于min_trx_id即已提交的事務(wù)中的值,所以我們事務(wù)200查詢(xún)結(jié)果就是xiaoming:

此時(shí)事務(wù)100將更新結(jié)果提交,因?yàn)榭芍貜?fù)讀生成readView永遠(yuǎn)是以第一次創(chuàng)建時(shí)候?yàn)橹鳎@也就意味著查詢(xún)的思路還是和上述步驟一樣,查詢(xún)結(jié)果仍然是xiaoming,這里就不多做贅述了。

5. 基于讀已提交版本readView理解SQL的MVCC工作機(jī)制

讀已提交版本會(huì)在每次執(zhí)行查詢(xún)時(shí)生成一個(gè)readView,我們還是以上面的例子進(jìn)行演示,還是事務(wù)100觸發(fā)修改但是還沒(méi)提交,對(duì)應(yīng)生成的版本鏈如下:

還是同理,執(zhí)行一些無(wú)關(guān)緊要的修改操作生成本次的事務(wù)id為200然后開(kāi)始查詢(xún),因?yàn)槭聞?wù)100沒(méi)有提交,所以活躍的事務(wù)列表數(shù)據(jù)為100、200生成readView如下:

所以順著版本鏈查詢(xún)到結(jié)果也是小于min_trx_id最大值為80,最終查詢(xún)結(jié)果為xiaoming。

然后事務(wù)100將結(jié)果提交,此時(shí)我們的事務(wù)200再次進(jìn)行查詢(xún),由讀已提交生成readView為每次查詢(xún)時(shí)可得,事務(wù)100已提交所以該事務(wù)處于已提交事務(wù)范圍,然后我們的事務(wù)200還未提交,所以處于活躍事務(wù)列表中,所以活躍事務(wù)列表只有我們的事務(wù)200:

由此順著版本鏈定位到小于min_trx_id的最大值為100,順著版本鏈定位到的第一個(gè)trx_id為100的結(jié)果是xiaoming_100,所以事務(wù)200查詢(xún)結(jié)果就是xiaoming_100。

6. MySQL 的隔離級(jí)別是基于鎖實(shí)現(xiàn)的嗎

是基于鎖和mvcc共同實(shí)現(xiàn)的,SERIALIZABLE 這個(gè)隔離級(jí)別就是基于鎖實(shí)現(xiàn)的,其他隔離級(jí)別都是基于mvcc,需要補(bǔ)充的是REPEATABLE-READ 如果使用當(dāng)前讀也是基于鎖實(shí)現(xiàn)。

7. MySQL 的默認(rèn)隔離級(jí)別是什么

以筆者使用的MySQL8來(lái)說(shuō)使用如下命令可以看到默認(rèn)級(jí)別為可重復(fù)讀:

select @@transaction_isolation;

對(duì)應(yīng)輸出結(jié)果如下:

@@transaction_isolation|
-----------------------+
REPEATABLE-READ        |

四、小結(jié)

MySQL 的 MVCC(多版本并發(fā)控制)是其實(shí)現(xiàn)高效并發(fā)處理的關(guān)鍵機(jī)制。

通過(guò) MVCC,在并發(fā)讀寫(xiě)操作時(shí),讀操作不會(huì)阻塞寫(xiě)操作,寫(xiě)操作也不會(huì)阻塞讀操作,極大地提高了數(shù)據(jù)庫(kù)的并發(fā)性和性能。

它允許事務(wù)讀取到特定版本的數(shù)據(jù),實(shí)現(xiàn)了事務(wù)隔離級(jí)別的靈活控制。使得不同的事務(wù)可以看到符合其隔離級(jí)別要求的數(shù)據(jù)視圖。

在 MVCC 中,每行數(shù)據(jù)都有多個(gè)版本,記錄了不同事務(wù)對(duì)其的修改歷史。這種方式有效地避免了鎖競(jìng)爭(zhēng)帶來(lái)的性能開(kāi)銷(xiāo)和潛在的死鎖問(wèn)題。

對(duì)于理解和優(yōu)化數(shù)據(jù)庫(kù)的并發(fā)操作,MVCC 是一個(gè)至關(guān)重要的概念。深入研究和掌握它,有助于更好地設(shè)計(jì)和管理數(shù)據(jù)庫(kù)系統(tǒng),確保數(shù)據(jù)的一致性和高效性。

責(zé)任編輯:趙寧寧 來(lái)源: 寫(xiě)代碼的SharkChili
相關(guān)推薦

2020-10-13 10:32:24

MySQL事務(wù)MVCC

2018-12-19 16:46:38

MySQL事務(wù)隔離數(shù)據(jù)庫(kù)

2021-08-04 13:19:42

MySQL 事務(wù)隔離

2021-07-26 10:28:13

MySQL事務(wù)隔離

2024-04-26 09:17:20

MySQL事務(wù)隔離

2024-12-02 08:37:04

2025-03-03 08:20:00

MySQL事務(wù)隔離數(shù)據(jù)庫(kù)

2009-06-29 17:54:47

Spring事務(wù)隔離

2010-11-19 16:13:06

oracle事務(wù)隔離級(jí)

2021-10-19 10:10:51

MySQL事務(wù)隔離級(jí)別數(shù)據(jù)庫(kù)

2022-06-10 11:51:49

MySQL事務(wù)隔離

2023-10-13 07:54:49

數(shù)據(jù)庫(kù)MySQL

2017-08-09 14:34:12

MysqlJavaPython

2020-03-05 09:33:15

數(shù)據(jù)庫(kù)事務(wù)隔離事務(wù)

2020-09-21 18:44:35

MySQL

2019-10-15 10:23:13

服務(wù)器MySQL 數(shù)據(jù)

2023-10-11 08:09:53

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

2021-12-27 09:20:13

事務(wù)模式隔離

2022-06-29 11:01:05

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

2023-09-22 08:27:39

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)