溫馨提示×

溫馨提示×

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

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

MySQL是怎么實現(xiàn)事務(wù)隔離性的

發(fā)布時間:2021-09-06 15:25:56 來源:億速云 閱讀:160 作者:chen 欄目:開發(fā)技術(shù)

這篇文章主要介紹“MySQL是怎么實現(xiàn)事務(wù)隔離性的”,在日常操作中,相信很多人在MySQL是怎么實現(xiàn)事務(wù)隔離性的問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”MySQL是怎么實現(xiàn)事務(wù)隔離性的”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

目錄
  • 并發(fā)場景

    • 寫-寫

    • 讀-讀

    • 讀-寫 和 寫-讀

  • MySQL中的鎖

    • 行級鎖

    • 表級鎖

  • 隔離級別

    • Read Committed

    • Repeatable Read


并發(fā)場景

最近做了一些分布式事務(wù)的項目,對事務(wù)的隔離性有了更深的認(rèn)識,后續(xù)寫文章聊分布式事務(wù)。今天就復(fù)盤一下單機事務(wù)的隔離性是如何實現(xiàn)的?

隔離的本質(zhì)就是控制并發(fā),如果SQL語句就是串行執(zhí)行的。那么數(shù)據(jù)庫的四大特性中就不會有隔離性這個概念了,也就不會有臟讀,不可重復(fù)讀,幻讀等各種問題了

對數(shù)據(jù)庫的各種并發(fā)操作,只有如下四種,寫寫,讀讀,讀寫和寫讀

寫-寫

事務(wù)A更新一條記錄的時候,事務(wù)B能同時更新同一條記錄嗎?

答案肯定是不能的,不然就會造成臟寫問題,那如何避免臟寫呢?答案就是加鎖

讀-讀

MySQL讀操作默認(rèn)情況下不會加鎖,所以可以并行的讀

讀-寫 和 寫-讀

基于各種場景對并發(fā)操作容忍程度不同,MySQL就搞了個隔離性的概念。你自己根據(jù)業(yè)務(wù)場景選擇隔離級別。

√ 為會發(fā)生,×為不會發(fā)生

隔離級別臟讀不可重復(fù)讀幻讀
read uncommitted(未提交讀)
read committed(提交讀)×
repeatable read(可重復(fù)讀)××
serializable (可串行化)×××

所以你看,MySQL是通過鎖和隔離級別對MySQL進行并發(fā)控制的

MySQL中的鎖

行級鎖

InnoDB存儲引擎中有如下兩種類型的行級鎖

  • 共享鎖(Shared Lock,簡稱S鎖),在事務(wù)需要讀取一條記錄時,需要先獲取改記錄的S鎖

  • 排他鎖(Exclusive Lock,簡稱X鎖),在事務(wù)要改動一條記錄時,需要先獲取該記錄的X鎖

如果事務(wù)T1獲取了一條記錄的S鎖之后,事務(wù)T2也要訪問這條記錄。如果事務(wù)T2想再獲取這個記錄的S鎖,可以成功,這種情況稱為鎖兼容,如果事務(wù)T2想再獲取這個記錄的X鎖,那么此操作會被阻塞,直到事務(wù)T1提交之后將S鎖釋放掉

如果事務(wù)T1獲取了一條記錄的X鎖之后,那么不管事務(wù)T2接著想獲取該記錄的S鎖還是X鎖都會被阻塞,直到事務(wù)1提交,這種情況稱為鎖不兼容。

多個事務(wù)可以同時讀取記錄,即共享鎖之間不互斥,但共享鎖會阻塞排他鎖。排他鎖之間互斥

S鎖和X鎖之間的兼容關(guān)系如下

兼容性X鎖S鎖
X鎖互斥互斥
S鎖互斥兼容

update,delete,insert 都會自動給涉及到的數(shù)據(jù)加上排他鎖,select 語句默認(rèn)不會加任何鎖

那什么情況下會對讀操作加鎖呢?

  • select … lock in share mode,對讀取的記錄加S鎖

  • select … for update ,對讀取的記錄加X鎖

  • 在事務(wù)中讀取記錄,對讀取的記錄加S鎖

  • 事務(wù)隔離級別在 SERIALIZABLE 下,對讀取的記錄加S鎖

InnoDB中有如下三種鎖

  • Record Lock:對單個記錄加鎖

  • Gap Lock:間隙鎖,鎖住記錄前面的間隙,不允許插入記錄

  • Next-key Lock:同時鎖住數(shù)據(jù)和數(shù)據(jù)前面的間隙,即數(shù)據(jù)和數(shù)據(jù)前面的間隙都不允許插入記錄

寫個Demo演示一下

CREATE TABLE `girl` (
  `id` int(11) NOT NULL,
  `name` varchar(255),
  `age` int(11),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into girl values
(1, '西施', 20),
(5, '王昭君', 23),
(8, '貂蟬', 25),
(10, '楊玉環(huán)', 26),
(12, '陳圓圓', 20);

Record Lock

對單個記錄加鎖

如把id值為8的數(shù)據(jù)加一個Record Lock,示意圖如下

MySQL是怎么實現(xiàn)事務(wù)隔離性的

Record Lock也是有S鎖和X鎖之分的,兼容性和之前描述的一樣。

SQL執(zhí)行加什么樣的鎖受很多條件的制約,比如事務(wù)的隔離級別,執(zhí)行時使用的索引(如,聚集索引,非聚集索引等),因此就不詳細(xì)分析了,舉幾個簡單的例子。

-- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主鍵進行等值查詢
-- 對id=8的記錄加S型Record Lock
select * from girl where id = 8 lock in share mode;

-- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主鍵進行等值查詢
-- 對id=8的記錄加X型Record Lock
select * from girl where id = 8 for update;

Gap Lock

鎖住記錄前面的間隙,不允許插入記錄

MySQL在可重復(fù)讀隔離級別下可以通過MVCC和加鎖來解決幻讀問題

當(dāng)前讀:加鎖
快照讀:MVCC

但是該如何加鎖呢?因為第一次執(zhí)行讀取操作的時候,這些幻影記錄并不存在,我們沒有辦法加Record Lock,此時可以通過加Gap Lock解決,即對間隙加鎖。

MySQL是怎么實現(xiàn)事務(wù)隔離性的

如一個事務(wù)對id=8的記錄加間隙鎖,則意味著不允許別的事務(wù)在id=8的記錄前面的間隙插入新記錄,即id值在(5, 8)這個區(qū)間內(nèi)的記錄是不允許立即插入的。直到加間隙鎖的事務(wù)提交后,id值在(5, 8)這個區(qū)間中的記錄才可以被提交

我們來看如下一個SQL的加鎖過程

-- REPEATABLE READ 利用主鍵進行等值查詢
-- 但是主鍵值并不存在
-- 對id=8的聚集索引記錄加Gap Lock
SELECT * FROM girl WHERE id = 7 LOCK IN SHARE MODE;

由于id=7的記錄不存在,為了禁止幻讀現(xiàn)象(避免在同一事務(wù)下執(zhí)行相同的語句得到的結(jié)果集中有id=7的記錄),所以在當(dāng)前事務(wù)提交前我們要預(yù)防別的事務(wù)插入id=7的記錄,此時在id=8的記錄上加一個Gap Lock即可,即不允許別的事務(wù)插入id值在(5, 8)這個區(qū)間的新記錄

MySQL是怎么實現(xiàn)事務(wù)隔離性的

給大家提一個問題,Gap Lock只能鎖定記錄前面的間隙,那么最后一條記錄后面的間隙該怎么鎖定?

其實mysql數(shù)據(jù)是存在頁中的,每個頁有2個偽記錄

  • Infimum記錄,表示該頁面中最小的記錄

  • upremum記錄,表示該頁面中最大的記錄

為了防止其它事務(wù)插入id值在(12, +∞)這個區(qū)間的記錄,我們可以給id=12記錄所在頁面的Supremum記錄加上一個gap鎖,此時就可以阻止其他事務(wù)插入id值在(12, +∞)這個區(qū)間的新記錄

Next-key Lock

同時鎖住數(shù)據(jù)和數(shù)據(jù)前面的間隙,即數(shù)據(jù)和數(shù)據(jù)前面的間隙都不允許插入記錄
所以你可以這樣理解Next-key Lock=Record Lock+Gap Lock

MySQL是怎么實現(xiàn)事務(wù)隔離性的

-- REPEATABLE READ 利用主鍵進行范圍查詢
-- 對id=8的聚集索引記錄加S型Record Lock
-- 對id>8的所有聚集索引記錄加S型Next-key Lock(包括Supremum偽記錄)
SELECT * FROM girl WHERE id >= 8 LOCK IN SHARE MODE;

因為要解決幻讀的問題,所以需要禁別的事務(wù)插入id>=8的記錄,所以

  • 對id=8的聚集索引記錄加S型Record Lock

  • 對id>8的所有聚集索引記錄加S型Next-key Lock(包括Supremum偽記錄)

表級鎖

表鎖也有S鎖和X鎖之分

在對某個表執(zhí)行select,insert,update,delete語句時,innodb存儲引擎是不會為這個表添加表級別的S鎖或者X鎖。

在對表執(zhí)行一些諸如ALTER TABLE,DROP TABLE這類的DDL語句時,會對這個表加X鎖,因此其他事務(wù)對這個表執(zhí)行諸如SELECT INSERT UPDATE DELETE的語句會發(fā)生阻塞

在系統(tǒng)變量autocommit=0,innodb_table_locks = 1時,手動獲取InnoDB存儲引擎提供的表t的S鎖或者X鎖,可以這么寫

對表t加表級別的S鎖

lock tables t read

對表t加表級別的X鎖

lock tables t write

如果一個事務(wù)給表加了S鎖,那么

  • 別的事務(wù)可以繼續(xù)獲得該表的S鎖

  • 別的事務(wù)可以繼續(xù)獲得表中某些記錄的S鎖

  • 別的事務(wù)不可以繼續(xù)獲得該表的X鎖

  • 別的事務(wù)不可以繼續(xù)獲得表中某些記錄的X鎖

如果一個事務(wù)給表加了X鎖,那么

  • 別的事務(wù)不可以繼續(xù)獲得該表的S鎖

  • 別的事務(wù)不可以繼續(xù)獲得表中某些記錄的S鎖

  • 別的事務(wù)不可以繼續(xù)獲得該表的X鎖

  • 別的事務(wù)不可以繼續(xù)獲得表中某些記錄的X鎖

所以修改線上的表時一定要小心,因為會使大量事務(wù)阻塞,目前有很多成熟的修改線上表的方法,不再贅述

隔離級別

讀未提交:每次讀取最新的記錄,沒有做特殊處理
串行化:事務(wù)串行執(zhí)行,不會產(chǎn)生并發(fā)

所以我們重點關(guān)注讀已提交可重復(fù)讀的隔離實現(xiàn)!

這兩種隔離級別是通過MVCC(多版本并發(fā)控制)來實現(xiàn)的,本質(zhì)就是MySQL通過undolog存儲了多個版本的歷史數(shù)據(jù),根據(jù)規(guī)則讀取某一歷史版本的數(shù)據(jù),這樣就可以在無鎖的情況下實現(xiàn)讀寫并行,提高數(shù)據(jù)庫性能

那么undolog是如何存儲修改前的記錄?

對于使用InnoDB存儲引擎的表來說,聚集索引記錄中都包含下面2個必要的隱藏列

trx_id:一個事務(wù)每次對某條聚集索引記錄進行改動時,都會把該事務(wù)的事務(wù)id賦值給trx_id隱藏列

roll_pointer:每次對某條聚集索引記錄進行改動時,都會把舊的版本寫入undo日志中。這個隱藏列就相當(dāng)于一個指針,通過他找到該記錄修改前的信息

如果一個記錄的name從貂蟬被依次改為王昭君,西施,會有如下的記錄,多個記錄構(gòu)成了一個版本鏈

MySQL是怎么實現(xiàn)事務(wù)隔離性的

為了判斷版本鏈中哪個版本對當(dāng)前事務(wù)是可見的,MySQL設(shè)計出了ReadView的概念。4個重要的內(nèi)容如下

  • m_ids:在生成ReadView時,當(dāng)前系統(tǒng)中活躍的事務(wù)id列表

  • min_trx_id:在生成ReadView時,當(dāng)前系統(tǒng)中活躍的最小的事務(wù)id,也就是m_ids中的最小值

  • max_trx_id:在生成ReadView時,系統(tǒng)應(yīng)該分配給下一個事務(wù)的事務(wù)id值

  • creator_trx_id:生成該ReadView的事務(wù)的事務(wù)id

當(dāng)對表中的記錄進行改動時,執(zhí)行insert,delete,update這些語句時,才會為事務(wù)分配唯一的事務(wù)id,否則一個事務(wù)的事務(wù)id值默認(rèn)為0。

max_trx_id并不是m_ids中的最大值,事務(wù)id是遞增分配的。比如現(xiàn)在有事務(wù)id為1,2,3這三個事務(wù),之后事務(wù)id為3的事務(wù)提交了,當(dāng)有一個新的事務(wù)生成ReadView時,m_ids的值就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4

MySQL是怎么實現(xiàn)事務(wù)隔離性的

執(zhí)行過程如下:

  • 如果被訪問版本的trx_id=creator_id,意味著當(dāng)前事務(wù)在訪問它自己修改過的記錄,所以該版本可以被當(dāng)前事務(wù)訪問

  • 如果被訪問版本的trx_id<min_trx_id,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView前已經(jīng)提交,所以該版本可以被當(dāng)前事務(wù)訪問

  • 被訪問版本的trx_id>=max_trx_id,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成ReadView后才開啟,該版本不可以被當(dāng)前事務(wù)訪問

  • 被訪問版本的trx_id是否在m_ids列表中

  • 4.1 是,創(chuàng)建ReadView時,該版本還是活躍的,該版本不可以被訪問。順著版本鏈找下一個版本的數(shù)據(jù),繼續(xù)執(zhí)行上面的步驟判斷可見性,如果最后一個版本還不可見,意味著記錄對當(dāng)前事務(wù)完全不可見

  • 4.2 否,創(chuàng)建ReadView時,生成該版本的事務(wù)已經(jīng)被提交,該版本可以被訪問

好了,我們知道了版本可見性的獲取規(guī)則,那么是怎么實現(xiàn)讀已提交和可重復(fù)讀的呢?

其實很簡單,就是生成ReadView的時機不同

舉個例子,先建立如下表

CREATE TABLE `girl` (
  `id` int(11) NOT NULL,
  `name` varchar(255),
  `age` int(11),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Read Committed

Read Committed(讀已提交),每次讀取數(shù)據(jù)前都生成一個ReadView

MySQL是怎么實現(xiàn)事務(wù)隔離性的

下面是3個事務(wù)執(zhí)行的過程,一行代表一個時間點

MySQL是怎么實現(xiàn)事務(wù)隔離性的

先分析一下5這個時間點select的執(zhí)行過程

  • 系統(tǒng)中有兩個事務(wù)id分別為100,200的事務(wù)正在執(zhí)行

  • 執(zhí)行select語句時生成一個ReadView,mids=[100,200],min_trx_id=100,max_trx_id=201,creator_trx_id=0(select這個事務(wù)沒有執(zhí)行更改操作,事務(wù)id默認(rèn)為0)

  • 最新版本的name列為西施,該版本trx_id值為100,在mids列表中,不符合可見性要求,根據(jù)roll_pointer跳到下一個版本

  • 下一個版本的name列王昭君,該版本的trx_id值為100,也在mids列表內(nèi),因此也不符合要求,繼續(xù)跳到下一個版本

  • 下一個版本的name列為貂蟬,該版本的trx_id值為10,小于min_trx_id,因此最后返回的name值為貂蟬

MySQL是怎么實現(xiàn)事務(wù)隔離性的

再分析一下8這個時間點select的執(zhí)行過程

  • 系統(tǒng)中有一個事務(wù)id為200的事務(wù)正在執(zhí)行(事務(wù)id為100的事務(wù)已經(jīng)提交)

  • 執(zhí)行select語句時生成一個ReadView,mids=[200],min_trx_id=200,max_trx_id=201,creator_trx_id=0

  • 最新版本的name列為楊玉環(huán),該版本trx_id值為200,在mids列表中,不符合可見性要求,根據(jù)roll_pointer跳到下一個版本

  • 下一個版本的name列為西施,該版本的trx_id值為100,小于min_trx_id,因此最后返回的name值為西施

當(dāng)事務(wù)id為200的事務(wù)提交時,查詢得到的name列為楊玉環(huán)。

Repeatable Read

Repeatable Read(可重復(fù)讀),在第一次讀取數(shù)據(jù)時生成一個ReadView

MySQL是怎么實現(xiàn)事務(wù)隔離性的

可重復(fù)讀因為只在第一次讀取數(shù)據(jù)的時候生成ReadView,所以每次讀到的是相同的版本,即name值一直為貂蟬,具體的過程上面已經(jīng)演示了兩遍了,我這里就不重復(fù)演示了,相信你一定會自己分析了。

到此,關(guān)于“MySQL是怎么實現(xiàn)事務(wù)隔離性的”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

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

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

AI