Mysql InnoDB多版本并發(fā)控制MVCC詳解
一丶為什么需要事務(wù)隔離級(jí)別
mysql是一個(gè)客戶端/服務(wù)端軟件,對(duì)于同一個(gè)服務(wù)器來(lái)說(shuō),可以有多個(gè)客戶端進(jìn)行連接,每一個(gè)客戶端進(jìn)行連接之后就形成一個(gè)會(huì)話,每一個(gè)客戶端都可以在自己的會(huì)話中向服務(wù)器發(fā)出請(qǐng)求語(yǔ)句,一個(gè)請(qǐng)求語(yǔ)句可能是某一個(gè)事務(wù)的一部分,服務(wù)器可以同時(shí)處理多個(gè)事務(wù)。
如果事務(wù)時(shí)一個(gè)接著一個(gè)進(jìn)行,那么下一個(gè)事務(wù)是在上一個(gè)事務(wù)的一致性前提下進(jìn)行的,就沒(méi)用一致性的問(wèn)題,但是事務(wù)是并發(fā)進(jìn)行且可能訪問(wèn)到相同的數(shù)據(jù)這時(shí)候就會(huì)出現(xiàn)如下問(wèn)題
可以看到AB最開(kāi)始總和13元,最后AB總和18元,銀行血虧五元,這顯然違背了一致性——錢(qián)的總量不變。這就是并發(fā)情況下兩個(gè)事務(wù)的影響,所以需要事務(wù)隔離
讓事務(wù)隔離的進(jìn)行,互不干涉。
1.實(shí)現(xiàn)事務(wù)隔離的方式:串行執(zhí)行
最簡(jiǎn)單直接的方式,同一時(shí)間只能有一個(gè)事務(wù)運(yùn)行,這樣必然不會(huì)有上述不一致的情況,但是大大降低了吞吐率并增加了事務(wù)的等待時(shí)間
2.實(shí)現(xiàn)事務(wù)隔離的方式:可串行執(zhí)行
并發(fā)事務(wù)之所以出現(xiàn)不一致的情況,就是由于多個(gè)事務(wù)訪問(wèn)相同的數(shù)據(jù),需要實(shí)現(xiàn)多個(gè)事務(wù)在訪問(wèn)相同數(shù)據(jù)的時(shí)候進(jìn)行限制,比方說(shuō)上圖中事務(wù)2想訪問(wèn)A賬戶的值需要等待事務(wù)提交事務(wù)之后,這樣可以讓并發(fā)事務(wù)的執(zhí)行如同串行執(zhí)行的效果一樣。
二丶并發(fā)事務(wù)執(zhí)行的問(wèn)題:臟寫(xiě),臟讀,不可重復(fù)讀,幻讀
1.臟寫(xiě)
一個(gè)事務(wù)修改了另外一個(gè)未提交事務(wù)修改過(guò)的數(shù)據(jù)
臟寫(xiě)導(dǎo)致一致性無(wú)法保證
上圖事務(wù)A和事務(wù)B都更新紫色數(shù)據(jù),其中事務(wù)A首先更新為A,然后事務(wù)B過(guò)來(lái)更新為B,這時(shí)候事務(wù)A回滾后更新為Null,事務(wù) B 明明正常寫(xiě)了一行數(shù)據(jù),但是寫(xiě)完之后發(fā)現(xiàn)值變了,有點(diǎn)丟失更新的意思。(比如A表示余額,這時(shí)候在將余額A判斷是否足以支付,判斷得到可以,事務(wù)B執(zhí)行扣費(fèi)寫(xiě)入A-5,商家收到5元,結(jié)果這時(shí)候回滾了,A變成Null,事務(wù)A中轉(zhuǎn)錢(qián)的一方錢(qián)變?yōu)锳,錢(qián)的總額變?yōu)锳+5了)
臟寫(xiě)導(dǎo)致原子性受到破壞
假如上述的事務(wù)B還操作了另外的數(shù)據(jù),比如插入一條數(shù)據(jù)C,并且更改為B寫(xiě)入C是在一個(gè)事務(wù)下面的,需要具備原子性,但是臟寫(xiě)讓B的更改需要部分回滾為Null,這樣插入C和更改B就不具備原子性(比如A表示余額,這時(shí)候在將余額A判斷是否足以支付,判斷得到可以,事務(wù)B執(zhí)行扣費(fèi)寫(xiě)入A-5,商家收到5元,結(jié)果這時(shí)候回滾了,A變成Null,這時(shí)候部分回滾,商家的5元沒(méi)用回滾,商家的庫(kù)存也沒(méi)用回滾,原子性被破壞)
2.臟讀
如果一個(gè)事務(wù)讀取到另外一個(gè)事務(wù)未提交的數(shù)據(jù),意味著發(fā)生了臟讀
比如事務(wù)A先寫(xiě)數(shù)據(jù)A,然后事務(wù)B督導(dǎo)數(shù)據(jù)A后在內(nèi)存中使用A進(jìn)行一系列操作(比如A表示余額,這時(shí)候在將余額A判斷是否足以支付,判斷得到可以)但是事務(wù)A這時(shí)候回滾了,事務(wù)B再次讀取數(shù)據(jù)發(fā)現(xiàn)為null,這就是臟讀。
臟讀可能引發(fā)一致性的問(wèn)題:比如事務(wù)操作時(shí)修改x和y的值,并且二者總是相等的,A修改x為1,還沒(méi)來(lái)得及修改y也沒(méi)用提交事務(wù),這時(shí)候事務(wù)B讀取x=1,y=0,二者不等,事務(wù)B讀取到了數(shù)據(jù)庫(kù)不一致的狀態(tài),讀取到未提交事務(wù)的值
3.不可重復(fù)讀
假如一個(gè)事務(wù)修改了另外一個(gè)事務(wù)未提交的數(shù)據(jù),意味發(fā)生了不可重復(fù)讀
比如事務(wù)A第一次讀取到值為A,接著事務(wù)B修改為B,并且提交了事務(wù)B,然后事務(wù)A再次讀取得到的數(shù)據(jù)是B,同一行數(shù)據(jù)多次讀取值并不相同,這稱作不可重復(fù)讀。它是指在同一個(gè)事務(wù)里面查詢同一行數(shù)據(jù),每次查到的數(shù)據(jù)都不一樣。和臟讀區(qū)別在于臟讀是由于別的事務(wù)回滾導(dǎo)致,而不可重復(fù)讀讀到的其實(shí)是已經(jīng)提交的數(shù)據(jù)。
事務(wù)A讀到事務(wù)B提交后的數(shù)據(jù)似乎很合理,但是我們想象這樣一種場(chǎng)景:你有一個(gè)流水表和用戶余額,其中記錄用戶每天的流水,你在月初0點(diǎn)的時(shí)候核對(duì)流水和庫(kù)存,但是流水很多,你的程序選擇一個(gè)一個(gè)用戶的進(jìn)行核對(duì),核對(duì)用戶甲,甲沒(méi)做任何消費(fèi),但是當(dāng)你核對(duì)B的時(shí)候,你將B的流水load到內(nèi)存中,但是B這時(shí)候(0點(diǎn)30分,這一筆數(shù)據(jù)新的一個(gè)余額)進(jìn)行了扣除余額的操作,導(dǎo)致B余額和流水對(duì)不上了。
4.幻讀
如果一個(gè)事務(wù)A先根據(jù)沒(méi)用搜索條件查詢到一些記錄,在該事務(wù)未提交前,另外一個(gè)事務(wù)寫(xiě)入(delete,update,insert)了符合搜索條件的記錄,這時(shí)候事務(wù)A再次讀取,發(fā)現(xiàn)數(shù)據(jù)條數(shù)和第一次讀取的不同,如同出現(xiàn)了幻覺(jué),稱之為幻讀
事務(wù)A讀到事務(wù)B提交后的數(shù)據(jù)似乎很合理,但是我們想象這樣一種場(chǎng)景:你有一個(gè)需求將會(huì)公司的男性員工了女性員工查詢進(jìn)行展示,你先查詢了總數(shù)為100人,然后查詢男性的總數(shù)50人,后查詢女性人數(shù)準(zhǔn)備在頁(yè)面展示共100人,其中男50人,女50人
,結(jié)果這是管理信息的人發(fā)現(xiàn)有一位員工性別錯(cuò)誤錄入了,將其從男修改為女,這時(shí)候你讀取事務(wù)就是女51人了,你在主頁(yè)顯示了共100人,其中男50人,女51人
三丶隔離級(jí)別
1.Read UnCommitted 讀未提交
在此隔離級(jí)別下,會(huì)發(fā)生臟讀,不可重復(fù)讀,和幻讀
2.Read Committed 讀已提交
在此隔離級(jí)別下,會(huì)發(fā)生不可重復(fù)讀,和幻讀
3.Repeatable Read 可重復(fù)讀
在此隔離級(jí)別下,可能發(fā)生幻讀
4.Serializable 可串行化
在此隔離級(jí)別下,不會(huì)發(fā)生臟讀,不可重復(fù)讀,和幻讀
其中臟寫(xiě)是對(duì)一致性影響最嚴(yán)重的,無(wú)論是何種隔離級(jí)別,都不允許臟寫(xiě)發(fā)生,innodb使用鎖保證不會(huì)出現(xiàn)臟寫(xiě)現(xiàn)象,第一個(gè)事務(wù)更新某條記錄的時(shí)候,會(huì)給這條記錄加鎖,另外一個(gè)事務(wù)在此更新的時(shí)候,需要等待第一個(gè)事務(wù)提交釋放鎖后更新。隔離級(jí)別越高,其并發(fā)能力越低。
四丶Mysql設(shè)置隔離級(jí)別
默認(rèn)隔離級(jí)別可重復(fù)讀
1.設(shè)置全局隔離級(jí)別
SET GLOBAL TRANSACTION ISOLATION LEVEL 期望的隔離級(jí)別(可選READ UNCOMMITED,READ COMMITED,REPEATABLE READ,SERIALIZABLE)
,此命令只對(duì)執(zhí)行語(yǔ)句后新產(chǎn)生的會(huì)話有效,對(duì)當(dāng)前已經(jīng)存在的會(huì)話無(wú)效
2.設(shè)置會(huì)話隔離級(jí)別
SET SESSION TRANSACTION ISOLATION LEVEL 期望的隔離級(jí)別(可選READ UNCOMMITED,READ COMMITED,REPEATABLE READ,SERIALIZABLE)
,對(duì)當(dāng)前會(huì)話后續(xù)事務(wù)有效,該語(yǔ)句可以在已開(kāi)啟的事務(wù)中執(zhí)行,但是不會(huì)影響當(dāng)前正在執(zhí)行的事務(wù),如果在事務(wù)之間執(zhí)行,只會(huì)對(duì)后續(xù)的事務(wù)有效
3.設(shè)置下一個(gè)事務(wù)的隔離級(jí)別
SET TRANSACTION ISOLATION LEVEL 期望的隔離級(jí)別(可選READ UNCOMMITED,READ COMMITED,REPEATABLE READ,SERIALIZABLE)
只對(duì)當(dāng)前會(huì)話的下一個(gè)即將開(kāi)啟的事務(wù)有效,下一個(gè)事務(wù)執(zhí)行完后,后續(xù)事務(wù)將恢復(fù)到之前的隔離級(jí)別,該語(yǔ)句不能再已經(jīng)開(kāi)啟的事務(wù)中執(zhí)行,否則會(huì)報(bào)錯(cuò)。
4.指定服務(wù)器的隔離級(jí)別
在啟動(dòng)的時(shí)候使用--transaction-isolation=xxx
即可執(zhí)行默認(rèn)隔離級(jí)別
五丶MVCC原理
下面討論記錄對(duì)當(dāng)前事務(wù)是否可見(jiàn)都是基于當(dāng)前事務(wù)中執(zhí)行的查詢是快照讀(普通查詢),對(duì)于當(dāng)前讀(select xxx for update,select xxx lock in share mode)是不通用的
1.版本鏈
對(duì)于InnoDB存儲(chǔ)引擎來(lái)說(shuō),其聚簇索引記錄中包含兩個(gè)隱藏列:
trx_id
:一個(gè)事務(wù)每次對(duì)聚簇索引記錄做出改動(dòng)的時(shí)候,都會(huì)把該事務(wù)的事務(wù)id復(fù)制給此列 roll_point
:每次對(duì)某條聚簇索引記錄進(jìn)行改動(dòng)的時(shí),都會(huì)把舊的版本寫(xiě)入到undo 日志中,此列相當(dāng)于一個(gè)指針,指向修改前的信息
每次修改都會(huì)形成Undo 日志,所有版本的數(shù)據(jù)會(huì)通過(guò)roll_point
串聯(lián)成一個(gè)鏈表,稱之為版本鏈,頭節(jié)點(diǎn)是當(dāng)前記錄的最新值。利用版本鏈控制多個(gè)并發(fā)事務(wù)訪問(wèn)相同記錄時(shí)的行為稱為MVCC多版本并發(fā)控制。
其實(shí)在undo日志中,只記錄被更新列的信息,而不是記錄全部的信息,對(duì)于沒(méi)有記錄的列,會(huì)通過(guò)版本鏈找少一個(gè)版本中的對(duì)應(yīng)列的信息,直到找到聚簇索引葉子節(jié)點(diǎn)中的內(nèi)容
2.Read View
對(duì)于使用Read Uncommitted隔離級(jí)別的事務(wù),可以讀取到?jīng)]提交的數(shù)據(jù),那么直接讀取最新的版本即可。對(duì)于Serializable隔離級(jí)別,innodb直接通過(guò)加鎖來(lái)訪問(wèn)記錄。對(duì)于read committed 和 repeatable read
隔離級(jí)別的事務(wù),都必須保證督導(dǎo)的數(shù)據(jù)是已經(jīng)提交事務(wù)修改過(guò)的記錄,那么如何判斷版本鏈中的哪個(gè)版本的數(shù)據(jù)是當(dāng)前事務(wù)可見(jiàn)的昵?
innodb 使用的Read View
2.1 read view 的結(jié)構(gòu)
- m_ids:在生成read view時(shí),當(dāng)前系統(tǒng)中活躍的讀寫(xiě)事務(wù)id列表
- min_trx_id:生成read view時(shí),當(dāng)前系統(tǒng)中活躍的讀寫(xiě)事務(wù)中最小事務(wù)id,也就是m_ids中的最小值
- max_trx_id:生成read view時(shí),系統(tǒng)應(yīng)該分配給下一個(gè)事務(wù)的事務(wù)id值
- creator_trx_id:生成該read view的事務(wù)的事務(wù)id
2.2 read view
- 如果被訪問(wèn)版本的
trx_id
和creator_trx_id
相同,意味著當(dāng)前事務(wù)在訪問(wèn)自己修改的記錄,自然可見(jiàn) - 如果訪問(wèn)版本的
trx_id
屬性值小于read view中的min_trx_id
表明此版本是生成read view之前已經(jīng)提交的事務(wù),那么自然可見(jiàn) - 如果訪問(wèn)版本的
trx_id
,大于等于read view中的max_trx_id
說(shuō)明,當(dāng)前版本數(shù)據(jù)是生成read view后開(kāi)啟事務(wù)產(chǎn)生的,那么自然不可見(jiàn) - 如果訪問(wèn)版本的
trx_id
介于min_trx_id
和max_trx_id
之間,需要判斷trx_id
是否位于m_ids
列表中,如果在說(shuō)明創(chuàng)建read view時(shí)生成該版本的事務(wù)還是活躍的,那么該版本,不可被訪問(wèn),如果不在說(shuō)明創(chuàng)建read view 時(shí)生成該版本的事務(wù)已經(jīng)提交,可以被訪問(wèn)到
如果某個(gè)版本數(shù)據(jù)對(duì)當(dāng)前事務(wù)不可見(jiàn)那么需要一直順著版本鏈找上一個(gè)版本的數(shù)據(jù),并通過(guò)上述步驟判斷是否可見(jiàn),直到找到可見(jiàn)的版本,如果一直找不到說(shuō)明該條記錄對(duì)當(dāng)前事務(wù)不可見(jiàn),查詢結(jié)果將不包含該記錄。
2.3 Read Committed和 Repeatable Read的不同
Read Committed——每次讀取數(shù)據(jù)前都生成一個(gè)Read View
這樣可以保證生成Read view 中的m_ids是實(shí)時(shí)活躍事務(wù)id集合,也許第一次讀取的時(shí)候事務(wù)A沒(méi)提交,其id位于m_ids中,但是第二次讀取的時(shí)候事務(wù)A提交了,事務(wù)A將不位于m_ids中,這樣在第二次讀取的時(shí)候,通過(guò)m_ids判斷事務(wù)A是否提交的時(shí)候,可以得到事務(wù)A已經(jīng)提交了,然后讓事務(wù)A版本產(chǎn)生的數(shù)據(jù)可見(jiàn)(見(jiàn)2.2.4中的內(nèi)容)。
Repeatable Read——如果使用begin開(kāi)啟事務(wù)那么在第一次查詢的時(shí)候生成Read view,如果使用start transaction with consistent snapshot
那么執(zhí)行的時(shí)候就會(huì)生成read view
這樣可以保證當(dāng)前事務(wù)從頭到尾都是read view中記錄的內(nèi)容是一致的,第一次讀取的時(shí)候事務(wù)A沒(méi)有提交,那么不可見(jiàn),但是第二次讀取的時(shí)候事務(wù)A提交了,但是read view的m_ids
和max_trx_id
可以判斷事務(wù)A不可見(jiàn),比如事務(wù)A事務(wù)id小于max_trx_id意味著生成read view是事務(wù)A啟動(dòng)但是沒(méi)提交,即使第二次讀事務(wù)A提交了,但是m_ids
中還是包含事務(wù)A,那么不可見(jiàn)。如果事務(wù)A事務(wù)id大于max_trx_id,那么自然第二次還是大于max_trx_id,也是不可見(jiàn)的,從而實(shí)現(xiàn)了可重復(fù)讀。
2.4 二級(jí)索引與MVCC
上面我們提到,innodb聚簇索引組織的記錄才具備trx_id
和roll_point
,那么我們使用二級(jí)索引進(jìn)行查詢的時(shí)候,如何判斷數(shù)據(jù)是否可見(jiàn)昵?
- 二級(jí)索引頁(yè)面的page header中存在page_max_trx_id屬性,每當(dāng)有事務(wù)對(duì)其中的記錄進(jìn)行增刪改查操作的時(shí)候,如果事務(wù)的事務(wù)id,大于page_max_trx_id,那么會(huì)更新page_max_trx_id屬性值為其事務(wù)id,這意味著page_max_trx_id記錄了修改該二級(jí)索引頁(yè)面最大的事務(wù)id是多少。當(dāng)select通過(guò)二級(jí)索引首先看下對(duì)于read view的min_trx_id是否大于該頁(yè)面的page_max_trx_id,如果大于那么頁(yè)面中所有記錄都對(duì)該read view可見(jiàn),否則就進(jìn)行下面的第二步
- 利用二級(jí)索引中的主鍵值,進(jìn)行回標(biāo),得到對(duì)應(yīng)的聚簇索引記錄然后進(jìn)行回表,然后通過(guò)2.2中步驟拿到第一個(gè)可見(jiàn)版本的數(shù)據(jù),然后比對(duì)此紀(jì)錄和通過(guò)二級(jí)索引查詢得到記錄的值是否相同,如果相同那么發(fā)送給客戶端,否則跳過(guò)該記錄。
到此這篇關(guān)于Mysql InnoDB多版本并發(fā)控制MVCC詳解的文章就介紹到這了,更多相關(guān)Mysql InnoDB多版本并發(fā)控制MVCC內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- MySQL多版本并發(fā)控制MVCC詳解
- Mysql MVCC多版本并發(fā)控制詳情
- MySQL的多版本并發(fā)控制MVCC的實(shí)現(xiàn)
- MySQL多版本并發(fā)控制MVCC底層原理解析
- MySQL多版本并發(fā)控制MVCC深入學(xué)習(xí)
- 詳解MySQL多版本并發(fā)控制機(jī)制(MVCC)源碼
- mysql的MVCC多版本并發(fā)控制的實(shí)現(xiàn)
- mysql多版本并發(fā)控制MVCC的實(shí)現(xiàn)
- 一文詳解MYSQL的多版本并發(fā)控制MVCC(Multi-Version Concurrency Control)
相關(guān)文章
淺談Using filesort和Using temporary 為什么這么慢
本文主要介紹了Using filesort和Using temporary為什么這么慢,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02mariadb集群搭建---Galera Cluster+ProxySQL教程
這篇文章主要介紹了mariadb集群搭建---Galera Cluster+ProxySQL教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03mysqldump命令導(dǎo)入導(dǎo)出數(shù)據(jù)庫(kù)方法與實(shí)例匯總
這篇文章主要介紹了mysqldump命令導(dǎo)入導(dǎo)出數(shù)據(jù)庫(kù)方法與實(shí)例匯總的相關(guān)資料,需要的朋友可以參考下2015-10-10關(guān)于mysql中string和number的轉(zhuǎn)換問(wèn)題
這篇文章主要介紹了關(guān)于mysql中string和number的轉(zhuǎn)換問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06Mysql聯(lián)合查詢UNION和UNION ALL的使用介紹
本文詳細(xì)介紹了Mysql的聯(lián)合查詢命令UNION和UNION ALL,總結(jié)了使用語(yǔ)法和注意事項(xiàng),以及學(xué)習(xí)例子和項(xiàng)目例子,需要的朋友可以參考下2014-04-04MySQL如何利用存儲(chǔ)過(guò)程快速生成100萬(wàn)條數(shù)據(jù)詳解
在MySQL數(shù)據(jù)庫(kù)中,如果要插入上百萬(wàn)級(jí)的記錄,用普通的insertinto來(lái)操作非常不現(xiàn)實(shí),速度慢人力成本高,這篇文章主要給大家介紹了關(guān)于MySQL如何利用存儲(chǔ)過(guò)程快速生成100萬(wàn)條數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2021-08-08Mysql查詢時(shí)間區(qū)間日期列表實(shí)例代碼
最近常用到mysql的日期范圍搜索,下面這篇文章主要給大家介紹了關(guān)于Mysql查詢時(shí)間區(qū)間日期列表的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04CentOS6.7 mysql5.6.33修改數(shù)據(jù)文件位置的方法
mysql存放的數(shù)據(jù)文件,分區(qū)容量較小,目前已經(jīng)滿,導(dǎo)致mysql連接不上,怎么解決呢?下面小編給大家分享CentOS6.7 mysql5.6.33修改數(shù)據(jù)文件位置的方法,一起看看吧2017-06-06