Mysql可重複讀(2) —— 快照真的就是快照嗎
上一講最後丟擲了一個問題,Mysql可重複讀的“快照”到底是啥?
是對當前資料的全量拷貝嗎?每開啟一個事務,都要把當前資料庫的資料拷貝一份出來?
很明顯不是。
一方面,這樣做太消耗記憶體了,另一方面,這樣會很慢。
那麼Mysql是如何實現“快照”的呢?
我們還是用上一講的例子:

我們已經知道,Session A在第二次select時,查詢到的結果和第一次select時一樣,也就是說,Session B的update,對Session A來說,不可見,Mysql是如何做到的呢?
很簡單,也很絕妙 —— 資料版本,也就是我們常說的MVCC,多版本併發控制。
下面講具體實現。
Innodb裡面,每行資料,都可以有多個版本,每個版本都有一個欄位 trx_id ,記錄生成這個版本的事務的ID。
假設一開始,id=1這行資料,只有一個版本,trx_id是90,意味著生成這個版本的事務ID是90:

這時候Session A開始了,從上一講,我們已經知道,begin時並不會生成快照,快照在第一次select時才會生成,那麼第一次select時,session A都做了什麼呢?
session A只需要做一件事:用一個數組,來記錄當前活躍的事務ID。
假設session A的事務ID是97,當前還有另外兩個事務,事務ID是94、96,所以session A會生成一個[94,96,97]的陣列。
這個陣列有什麼用?後面你就知道了。
接著,session B執行了update語句,來更新id=1這一行資料,給這一行資料生成一個新的版本,假設session B的事務ID是98,因此這行資料就有了兩個版本:

這時候,session A又來select了,當前版本是session B生成的,那session A是如何找到之前的版本的呢?
這時候,session A一開始生成的事務陣列就派上用場了,session A的事務陣列是[94,96,97],最小事務ID是94,最大事務ID是97,所以,當它遇到一行資料時,會先判斷這行資料的版本號X:
- 如果X小於94,那麼意味著這行資料,在session A開始前就已經提交了,應該對session A可見
- 如果X大於97,那麼意味著這行資料,是在session A開始之後,才提交的,應該對session A不可見
- 如果X在位於[94,97]這個區間內,那麼分兩種情況:
- 如果X在數組裡面,比如X是96,那麼意味著,當session A開始時,生成這個版本的資料的事務,還沒提交,因此這行資料對Session A不可見
- 如果X不在數組裡面,比如X是95,那麼意味著,當session A開始時,生成這個版本的資料的事務,已經提交,因此這行資料對Session A可見
好,現在session A開始遍歷id=1這行資料的所有版本:

當前版本是98,大於97,所以不可見,繼續看上一個版本;
再往上,版本是90,小於94,可見,就它了,所以session A select出來的id=1的資料,c的值是1。
當然,這樣的人肉判斷實在太麻煩了,在《Mysql實戰45講》裡,丁奇給出了這樣一個 “等價判斷”可見性的原則 :
- 版本未提交,不可見;
- 版本已提交,但是是在快照建立後提交的,不可見;
- 版本已提交,而且是在快照建立前提交的,可見。
這其實就是可重複讀的想要實現的效果。
最後再給一個複雜點的例子,大家運用上面的原則,來預測sql語句的查詢結果:

小結一下:
- “快照”不是全量拷貝,而是利用了資料多版本的特性,也就是MVCC
- MVCC的核心在於每個事務自己維護的一個事務ID陣列
- 可以用“等價原則”來判斷資料版本的可見性
問題又來了,這些不同版本的資料,是物理存在於記憶體或者磁碟中的嗎?

參考
- 《Mysql實戰45講》丁奇
- Innodb Transaction Isolation Levels