隔離級別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|
read uncommitted(未提交讀) | √ | √ | √ |
read committed(提交讀) | × | √ | √ |
repeatable read(可重復(fù)讀) | × | × | √ |
serializable (可串行化) | × | × | × |
所以你看,MySQL是通過鎖和隔離級別對MySQL進(jìn)行并發(fā)控制的
InnoDB存儲引擎中有如下兩種類型的行級鎖
如果事務(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)不會加任何鎖
那什么情況下會對讀操作加鎖呢?
InnoDB中有如下三種鎖
寫個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,示意圖如下
Record Lock也是有S鎖和X鎖之分的,兼容性和之前描述的一樣。
SQL執(zhí)行加什么樣的鎖受很多條件的制約,比如事務(wù)的隔離級別,執(zhí)行時使用的索引(如,聚集索引,非聚集索引等),因此就不詳細(xì)分析了,舉幾個簡單的例子。
-- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主鍵進(jìn)行等值查詢 -- 對id=8的記錄加S型Record Lock select * from girl where id = 8 lock in share mode; -- READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ 利用主鍵進(jìn)行等值查詢 -- 對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解決,即對間隙加鎖。
如一個事務(wù)對id=8的記錄加間隙鎖,則意味著不允許別的事務(wù)在id=8的記錄前面的間隙插入新記錄,即id值在(5, 8)這個區(qū)間內(nèi)的記錄是不允許立即插入的。直到加間隙鎖的事務(wù)提交后,id值在(5, 8)這個區(qū)間中的記錄才可以被提交
我們來看如下一個SQL的加鎖過程
-- REPEATABLE READ 利用主鍵進(jìn)行等值查詢 -- 但是主鍵值并不存在 -- 對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ū)間的新記錄
給大家提一個問題,Gap Lock只能鎖定記錄前面的間隙,那么最后一條記錄后面的間隙該怎么鎖定?
其實mysql數(shù)據(jù)是存在頁中的,每個頁有2個偽記錄
為了防止其它事務(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
-- REPEATABLE READ 利用主鍵進(jìn)行范圍查詢 -- 對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的記錄,所以
表鎖也有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鎖,那么
所以修改線上的表時一定要小心,因為會使大量事務(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ù)每次對某條聚集索引記錄進(jìn)行改動時,都會把該事務(wù)的事務(wù)id賦值給trx_id隱藏列
roll_pointer:每次對某條聚集索引記錄進(jìn)行改動時,都會把舊的版本寫入undo日志中。這個隱藏列就相當(dāng)于一個指針,通過他找到該記錄修改前的信息
如果一個記錄的name從貂蟬被依次改為王昭君,西施,會有如下的記錄,多個記錄構(gòu)成了一個版本鏈
為了判斷版本鏈中哪個版本對當(dāng)前事務(wù)是可見的,MySQL設(shè)計出了ReadView的概念。4個重要的內(nèi)容如下
當(dāng)對表中的記錄進(jìn)行改動時,執(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
執(zhí)行過程如下:
好了,我們知道了版本可見性的獲取規(guī)則,那么是怎么實現(xiàn)讀已提交和可重復(fù)讀的呢?
其實很簡單,就是生成ReadView的時機(jī)不同
舉個例子,先建立如下表
CREATE TABLE `girl` ( `id` int(11) NOT NULL, `name` varchar(255), `age` int(11), PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Read Committed(讀已提交),每次讀取數(shù)據(jù)前都生成一個ReadView
下面是3個事務(wù)執(zhí)行的過程,一行代表一個時間點
先分析一下5這個時間點select的執(zhí)行過程
再分析一下8這個時間點select的執(zhí)行過程
當(dāng)事務(wù)id為200的事務(wù)提交時,查詢得到的name列為楊玉環(huán)。
Repeatable Read(可重復(fù)讀),在第一次讀取數(shù)據(jù)時生成一個ReadView
可重復(fù)讀因為只在第一次讀取數(shù)據(jù)的時候生成ReadView,所以每次讀到的是相同的版本,即name值一直為貂蟬,具體的過程上面已經(jīng)演示了兩遍了,我這里就不重復(fù)演示了,相信你一定會自己分析了。
[1]https://souche.yuque.com/bggh1p/8961260/gyzlaf
[2]https://zhuanlan.zhihu.com/p/35477890
到此這篇關(guān)于MySQL事務(wù)的隔離性是如何實現(xiàn)的的文章就介紹到這了,更多相關(guān)MySQL事務(wù)的隔離性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
標(biāo)簽:西寧 汕尾 寶雞 七臺河 邯鄲 無錫 營口 來賓
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《MySQL事務(wù)的隔離性是如何實現(xiàn)的》,本文關(guān)鍵詞 MySQL,事務(wù),的,隔,離性,是,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。