Mysql的隔離級別分為: 讀未提交、讀已提交、可重復讀、串行讀
比較常用的兩種分別是讀已提交、可重復讀,那么Mysql是如何保證多個事務(wù)讀取一條數(shù)據(jù)的隔離性的?
undo Log
當我們讀取一條被其他事務(wù)變更的數(shù)據(jù)時,會在undo Log中產(chǎn)生一條變更前的日志.這個日志可以專門用于回滾。
我們大概來看一下這個日志的大概結(jié)構(gòu):

前面三個字段屬于變更前的,另外:
trx_id : 代表是哪個事務(wù)編號修改的。
需要注意的是該編號是嚴格按照遞增順序產(chǎn)生的。比如1、2、3、4、5
roll_pointer : 相當于一個鏈表,往下查找就是上一次更改前的。當一條數(shù)據(jù)被更改了多次之后,由該字段構(gòu)建成一個鏈表俗稱版本鏈。
有了undo Log的話可以很快追溯到更改之前的數(shù)據(jù),有了這個之后,假設(shè)多個事務(wù)都在讀同一條記錄,并且還發(fā)生了修改,這個時候
- 哪些能讀?
- 哪些版本才是符合當前事務(wù)該看到的數(shù)據(jù)呢?
MVCC
多版本并發(fā)控制,指的就是在使用讀提交和可重復讀隔離級別的事務(wù),在執(zhí)行普通select操作時,訪問記錄版本鏈的過程;使不同事務(wù)的讀寫、寫讀操作并發(fā)執(zhí)行,提高系統(tǒng)性能;
Read View 讀視圖
基于當前活躍事務(wù)列表構(gòu)成的ReadView,當某個事務(wù)創(chuàng)建ReadView時,會將當前活躍的事務(wù)也加入其中。
我們來看一下大概結(jié)構(gòu):

readview中四個比較重要的概念:
m_ids:表示在生成readview時,當前系統(tǒng)中活躍的讀寫事務(wù)id列表;
比如創(chuàng)建事務(wù)10創(chuàng)建RV時,系統(tǒng)正在活躍的事務(wù)有5,6,7那么5,6,7都會加入到10的m_ids中.
min_trx_id:表示在生成readview時,當前系統(tǒng)中活躍的讀寫事務(wù)中最小的事務(wù)id,也就是m_ids中最小的值;
max_trx_id:表示生成readview時,系統(tǒng)中應(yīng)該分配給下一個事務(wù)的id值;
creator_trx_id:表示生成該readview的事務(wù)的事務(wù)id;
有了readview,在訪問某條記錄時,按照以下步驟判斷記錄的某個版本是否可見:
基于可重復讀
下面是對于同一條數(shù)據(jù)的多個事務(wù)讀取流程:

ReadView(簡稱RV)一旦創(chuàng)建是不可變的,即便其中某個線程事務(wù)提交了,也不會影響當前線程創(chuàng)建的ReadView,你可以理解為一個副本快照。
總的來說判斷就三個條件:
- undo log的數(shù)據(jù)中包含的trx_id是否符合min_trx_id和max_trx_id之間
1.1 如果小于min_trx_id說明創(chuàng)建RV 之前 的時候這個trx_id就已經(jīng)事務(wù)提交了,不活躍了,說明可以讀。
1.2 如果大于max_trx_id說明這個版本是在創(chuàng)建RV 之后 產(chǎn)生的,不可讀。因為創(chuàng)建RV時你這個版本還不存在。
1.3 如果是在這之間的再看步驟2 - 查看trx_id是否包含m_id之中:
2.1 包含說明創(chuàng)建RV的時候,還是活躍(沒提交)事務(wù)。那么是不可見的,臟讀;繼續(xù)看步驟3
2.2 不包含說明創(chuàng)建RV之前這個事務(wù)已經(jīng)被提交了,那么是可見的。 - 到了這里說明這條數(shù)據(jù)的變更版本在RV之內(nèi),則要查看creator_trx_id與trx_id是否一致:
3.1 一致說明就是當前事務(wù)創(chuàng)建的;允許使用;
3.2 否則說明是當前RV的其他事務(wù)操作的不能使用;
基于上述規(guī)則,很好的解決了一致性讀的問題;當前線程創(chuàng)建完RV之后,讀到的數(shù)據(jù)都是相同的;不會讀到其他事務(wù)未提交和后提交的數(shù)據(jù)。
可重復讀的RV是以一個事務(wù)的開始和結(jié)束作為它的生命周期的
基于讀提交級別的流程
讀提交級別是能夠讀到其他事務(wù)提交的數(shù)據(jù)的,那么這個時候上面的流程是不是滿足不了呀?因為假設(shè)ABC都在一個RV之中,C提交了數(shù)據(jù),但是B看不到呀,因為條件2就滿足不了呀;
這個時候Mysql就把這個級別的RV做了調(diào)整,每次讀取數(shù)據(jù)的時候會創(chuàng)建一個新的ReadView

當RV中的事務(wù)B提交了事務(wù)的時候,A每次會創(chuàng)建一個新的RV來查看數(shù)據(jù)版本,新的RV的m_ids肯定是不包含已經(jīng)提交的事務(wù)B的,這個時候就能夠讀到事務(wù)B的數(shù)據(jù)了。
之前一直以為可重復讀沒有解決幻讀的問題,現(xiàn)在基于這個流程另外加上命令行調(diào)試之后發(fā)現(xiàn)應(yīng)該是解決了的。
因為一旦創(chuàng)建RV的話,當前活躍事務(wù)快照已經(jīng)生成,這個時候如果新來的事務(wù)或者快照內(nèi)的事務(wù)新增了數(shù)據(jù)也不會讀到:
- 快照事務(wù)內(nèi)產(chǎn)生的變更在前面說的2.1會被跳過
- 快照之后會在1.2被過跳過
如有問題,歡迎留言交流。