之前已經轉載過幾篇相關的文章,此次基於mysql 5.7 版本,從測試和源碼角度解釋一下RR,RC級別為什么看到的數據不一樣
先補充一下基礎知識
基本知識
假設對於多版本(MVCC)的基礎知識,有所了解。InnoDB為了實現多版本的一致讀,采用的是基於回滾段的協議。
行結構
InnoDB表數據的組織方式為主鍵聚簇索引。由於采用索引組織表結構,記錄的ROWID是可變的(索引頁分裂的時候,Structure Modification Operation,SMO),因此二級索引中采用的是(索引鍵值, 主鍵鍵值)的組合來唯一確定一條記錄。
無論是聚簇索引,還是二級索引,其每條記錄都包含了一個DELETED BIT位,用於標識該記錄是否是刪除記錄。除此之外,聚簇索引記錄還有兩個系統列:DATA_TRX_ID,DATA_ROLL_PTR。DATA _TRX_ID表示產生當前記錄項的事務ID;
DATA _ROLL_PTR指向當前記錄項的undo信息。
開啟4個mysql客戶端,client1 用來查看數據情況 , client2,client3,client4 更新數據
表結構
所有client 啟動事務
執行 show engine innodb status\G , 觀察目前的事務信息
這里可以看到目前沒有任何活動事務(select 不產生任何活動事務,只有insert ,update,delete 才會產生活動事務)
我們先在client2 執行一個insert sql,然后再在client1 看事務信息
我們可以看到client2 產生了12654 的活動事務, 我們繼續再client3執行sql
client3 產生了一個12659的事務
我們從截圖的角度看這2個事務
12659 -- 這是物理位置比較高的事務, mysql 本身里有專有名詞叫 low_limit_id ,高水位
12654 --這是物理位置比較低的事務, up_limit_id ,低水位
接下來我們在client1 執行select查詢
client2,client3還沒有提交,這時候client1 只能獲取到以前的舊數據
這時候client2,client3提交,再在client1查詢
重點來了,這時候mysql產生了一份快照,這里叫read_view, 快照的結構是
read_view->creator_trx_id = 當前client1的事務id;
read_view->up_limit_id = 12654;
read_view->low_limit_id = 12659;
read_view->trx_ids = [12654,12659];
read_view->m_trx_ids = 2;
接下來,我們在client4執行insert操作,並馬上commit提交,然后再在client1執行select查詢
我們可以看到client4的修改雖然已經提交,但是client1還是看不到這個變化,那是因為:
low_limit_id; /* 索引數據事務號 DATA_TRX_ID >= low_limit_id的記錄,對於當前Read View都是不可見的 */
up_limit_id; /* 索引數據事務號 DATA_TRX_ID< up_limit_id ,對於當前Read View都是可見的 */
剛才client4修改的時候產生的事務id是比12659還要大的一個數字, 沒有查看,不知道具體數字,我們假設是12700這個數字
當產生read-view的時候, low_limit_id 是 12659,up_limit_id 是12654, 12700>12659 & 12654, 所以這些12654,12659對於那個read-view來說是看不見的
如果在client1 select 產生read-view 之前就執行client4的修改(和提交, 這時候client4的事務id, 我們假設就是一個比12654小的數字,如12600),12600<12654, 所以client4的修改對於當前read-view就是可視的
我們看看源碼 read0types.h
那為什么事務隔離級別是RC級別的時候,重復上面的操作步驟, client1在client2,client3提交后select 的時候,就能馬上查詢到修改
我們看源碼
ha_innodb.cc的ha_innobase::external_lock方法里
在RC級別的時候,每次都會關閉read-view並產生一份新的