基本原理
MVCC的實作,通過儲存資料在某個時間點的快照來實作的。這意味着一個事務無論運作多長時間,在同一個事務裡能夠看到資料一緻的視圖。根據事務開始的時間不同,同時也意味着在同一個時刻不同僚務看到的相同表裡的資料可能是不同的。
每行資料都存在一個版本,每次資料更新時都更新該版本。
修改時Copy出目前版本随意修改,各個事務之間無幹擾。
儲存時比較版本号,如果成功(commit),則覆寫原記錄;失敗則放棄copy(rollback)
在每一行資料中額外儲存兩個隐藏的列:目前行建立時的版本号和删除時的版本号。這裡的版本号并不是實際的時間值,而是系統版本号。每開始新的事務,系統版本号都會自動遞增。事務開始時刻的系統版本号會作為事務的版本号,用來和查詢每行記錄的版本号進行比較。
每個事務又有自己的版本号,這樣事務内執行CRUD操作時,就通過版本号的比較來達到資料版本控制的目的。
我們來具體看看是如何實作的。
版本鍊
我們先來了解一下版本鍊的概念。在InnoDB引擎表中,它的聚簇索引記錄中有兩個必要的隐藏列:
trx_id這個id用來存儲的每次對某條聚簇索引記錄進行修改的時候的事務id。
roll_pointer每次對哪條聚簇索引記錄有修改的時候,都會把老版本寫入undo日志中。這個roll_pointer就是存了一個指針,它指向這條聚簇索引記錄的上一個版本的位置,通過它來獲得上一個版本的記錄資訊。(注意插入操作的undo日志沒有這個屬性,因為它沒有老版本)

比如現在有個事務id是60的執行的這條記錄的修改語句
此時在undo日志中就存在版本鍊
ReadView
說了版本鍊我們再來看看ReadView。已送出讀和可重複讀的差別就在于它們生成ReadView的政策不同。
舉個例子 ,在已送出讀隔離級别下:
比如此時有一個事務id為100的事務,修改了name,使得的name等于小明2,但是事務還沒送出。則此時的版本鍊是
那此時另一個事務發起了select 語句要查詢id為1的記錄,那此時生成的ReadView 清單隻有[100]。那就去版本鍊去找了,首先肯定找最近的一條,發現trx_id是100,也就是name為小明2的那條記錄,發現在清單内,是以不能通路。
這時候就通過指針繼續找下一條,name為小明1的記錄,發現trx_id是60,小于清單中的最小id,是以可以通路,直接通路結果為小明1。
那這時候我們把事務id為100的事務送出了,并且建立了一個事務id為110也修改id為1的記錄,并且不送出事務
這時候版本鍊就是
這時候之前那個select事務又執行了一次查詢,要查詢id為1的記錄。
如果你是已送出讀隔離級别,這時候你會重新生成一個ReadView,那你的活動事務清單中的值就變了,變成了[110]。
按照上的說法,你去版本鍊通過trx_id對比查找到合适的結果就是小明2。
如果你是可重複讀隔離級别,這時候你的ReadView還是第一次select時候生成的ReadView,也就是清單的值還是[100]。是以select的結果是小明1。是以第二次select結果和第一次一樣,是以叫可重複讀!
也就是說已送出讀隔離級别下的事務在每次查詢的開始都會生成一個獨立的ReadView,而可重複讀隔離級别則在第一次讀的時候生成一個ReadView,之後的讀都複用之前的ReadView。
客觀上,我們認為他就是樂觀鎖的一整實作方式,就是每行都有版本号,儲存時根據版本号決定是否成功。但由于Mysql的寫操作會加排他鎖(前文有講),如果鎖定了還算不算是MVCC?了解樂觀鎖的小夥伴們,都知道其主要依靠版本控制,即消除鎖定,二者互相沖突,so從某種意義上來說,Mysql的MVCC并非真正的MVCC,他隻是借用MVCC的名号實作了讀的非阻塞而已。