問題: 有開發(fā)問到,為啥我在一個事務(wù)中刪除數(shù)據(jù)沒有報錯,還能select查到呢? 其實這是隔離級別的原因,不同隔離級別帶來的表現(xiàn)是不同的,下面主要給大家介紹下RC和RR隔離級別的一些原理
本文主要介紹RR和RC在事務(wù)中如何訪問數(shù)據(jù)的,導(dǎo)語中的這個問題我們在最后會說明原因
首先大家需要先了解一個視圖的概念,這個視圖并不是指view這種虛擬表,而是指innodb在實現(xiàn)MVCC時用到的一致性讀視圖,即consistent read view,用于實現(xiàn)RC和RR隔離級別的實現(xiàn)
RR和RC隔離級別下什么時候會產(chǎn)生這個視圖呢?
- RR:事務(wù)啟動時創(chuàng)建的
- 這里需要注意,如果是用begin或者start transaction的方式啟動事務(wù)的話,需要在執(zhí)行第一個操作innodb表的語句才會創(chuàng)建這個視圖
- start transaction with consistent snapshot會直接創(chuàng)建這個一致性視圖
- RC:每個SQL語句開始執(zhí)行的時候創(chuàng)建的
MVCC中不同隔離級別如何訪問數(shù)據(jù)的
RR和RC隔離級別的區(qū)別,先說結(jié)論:
- RR:
1、針對查詢:快照讀,以建立的一致性視圖為主,只能查看在一致性快照前就已經(jīng)提交完成的數(shù)據(jù)
2、針對變更:先進(jìn)行當(dāng)前讀在進(jìn)行變更,每次變更的都是最新的數(shù)據(jù) - RC:
1、所有都是當(dāng)前讀:只能查看在語句啟動前就已經(jīng)提交完成的數(shù)據(jù)
怎么判斷具體哪些數(shù)據(jù)是可以訪問的呢?這里我們需要了解一下在事務(wù)中訪問數(shù)據(jù)時都做了什么操作
- innodb 里面每個事務(wù)都有一個唯一的事務(wù)ID,transaction id,在事務(wù)開始時向事務(wù)系統(tǒng)申請的遞增唯一值
- 每次數(shù)據(jù)更新都會有一個版本,row trx_id,所以每行數(shù)據(jù)可能會有多個版本,然后多個版本通過undo關(guān)聯(lián)起來,這就是MVCC
- RR就是以一致性快照創(chuàng)建的時刻為準(zhǔn),在此時刻前已經(jīng)提交的,可見,自己事務(wù)內(nèi)執(zhí)行的數(shù)據(jù)版本也會認(rèn),數(shù)據(jù)變更需要先獲取最新數(shù)據(jù)
- RC就是每個語句執(zhí)行執(zhí)行都會進(jìn)行一次檢查,只要是在我這個語句之前的已經(jīng)提交完成的數(shù)據(jù)就認(rèn)
那么問題來了,如果RR級別下,如何判斷row trx_id是否可見呢
這里innodb為每個事務(wù)都構(gòu)造了一個數(shù)組,用來保存這個一致性快照啟動瞬間,當(dāng)前已經(jīng)啟動未提交的所有事務(wù)id,這個數(shù)組里面里面最小的事務(wù)ID為低水位,事務(wù)ID最大值+1是高水位線,低于低水位線的都是提交了的,高于高水位線的都是未來創(chuàng)建的事務(wù),最低水位線和最高水位線之間的row trx_id
如果在這個數(shù)組內(nèi),則未提交,不在數(shù)組內(nèi),則提交

一個事務(wù)中,哪些數(shù)據(jù)是可以訪問的呢?
- 落在綠色區(qū)域,證明是在一致性視圖創(chuàng)建前已經(jīng)提交的事務(wù)或者自己生成的事務(wù),可見
- 落在紅色部分,說明是在一致性視圖創(chuàng)建后啟動的事務(wù),所以不可見
- 落在中間部分
3.1、若 row trx_id 在數(shù)組中,表示這個版本是由還沒提交的事務(wù)生成的,不可見;
3.2、若 row trx_id 不在數(shù)組中,表示這個版本是已經(jīng)提交了的事務(wù)生成的,可見
這里有個誤區(qū):
很多人會把這個高水位線當(dāng)做當(dāng)前事務(wù)的事務(wù)id+1,但其實不是的,當(dāng)前事務(wù)如果啟動了只做了update操作,也會分配一個事務(wù)id,但是此時卻不會生成read-view,只有在執(zhí)行第一個select時才會產(chǎn)生這個一致性視圖,此時可能也產(chǎn)生了其他事務(wù),所以這個高水位線還是要理解為創(chuàng)建一致性快照時已經(jīng)創(chuàng)建過的最大事務(wù)id+1
表A,只有id,是主鍵(如果不是主鍵RR隔離級別更新就會被鎖住了),數(shù)據(jù)1,2,3,4
| 時間 | 事務(wù)A | 事務(wù)B | 事務(wù)C | 事務(wù)D |
|---|---|---|---|---|
| T1 | begin;update A set id=5 where id=1;commit; trx:1 | |||
| T2 | begin ;update A set id=6 where id=2; trx:2 | |||
| T3 | begin; update A set id=7 where id=3;commit; trx 3 | |||
| T4 | begin; update A set id=8 where id=4,; trx:4 | |||
| T4 | select * from A |
T4時刻事務(wù)B生成一致性視圖時最低水位線是2,最高水位線就是4+1=5,數(shù)組為(2,4)
- trx1是低于最低水位線,代表已提交,可見
- trx2是自己更新的,可見
- trx3不在數(shù)組中,可見
- trx4在數(shù)組中,不可見
因此在T4時刻,事務(wù)B讀到的數(shù)據(jù)應(yīng)該就是5,6,7,4
問題解決
我們現(xiàn)在回到開頭的問題
問題:開發(fā)說在一個事務(wù)中刪除了一個數(shù)據(jù),但是select還能看到
- begin;select * from aaa id =1;有一個值
- delete from aaa where id=1; 無報錯
- select * from aaa id =1;還是有那個值
排查原因:開發(fā)是多線程并發(fā)執(zhí)行相同的操作,在某個時間點會出現(xiàn)delete語句執(zhí)行前在另一個事務(wù)已經(jīng)執(zhí)行完了上述操作,將id=1的值已經(jīng)刪除掉了
| 時間 | 事務(wù)A | 事務(wù)B |
|---|---|---|
| T1 | begin;select * from aaa where id=1;#有值 | |
| T2 | begin;delete from aaa where id=1;commit; | |
| T3 | delete from aaa where id=1;#這里需要注意這種情況出現(xiàn)的時候返回行數(shù)為0 | |
| T4 | select * from aaa where id=1;#有值 |
原因一目了然
- 事務(wù)A在T1時刻已經(jīng)建立了一致性視圖,
- 事務(wù)B在T2時刻刪除了id=1的數(shù)據(jù)并且提交了
- 事務(wù)A在T3時刻進(jìn)行delete操作時進(jìn)行當(dāng)前讀已經(jīng)沒有id=1的數(shù)據(jù),所以雖然沒有報錯但是返回行數(shù)為0
- 事務(wù)A在T4時刻select時相當(dāng)于還是進(jìn)行了快照讀,因為在T3時刻相當(dāng)于沒有對事務(wù)進(jìn)行任何修改,所以讀到還是有數(shù)據(jù)
所以這種并行操作時遇到這種情況可以結(jié)合下返回行數(shù)來進(jìn)行判斷,或者隔離級別看是否可以改為RC
本文重點介紹了RR,RC隔離級別下事務(wù)內(nèi)如何訪問數(shù)據(jù)的,一致性快照是何時產(chǎn)生的以及如果結(jié)合一致性快照判斷哪些數(shù)據(jù)是可以訪問的,水平有限,如有理解問題辛苦各位大佬指正