MVCC多版本並發控制的理解

前置知識

當前讀與快照讀

當前讀
什麼是當前讀:讀取的是最新的數據,不會讀到老數據。
何時觸發:update、insert、delete、select lock in share mode、select for update時,總是當前讀。
快照讀
什麼是快照讀:讀取的是歷史版本,不是最新的數據。
何時觸發:select

這些關係一定要搞清楚!

事務的ACID

原子性:事務要麼全部成功,要麼全部失敗。實現:undolog回滾日誌實現。相當於存儲在磁盤中的歷史記錄鏈、還有一個更官方的名字:快照
一致性:由另外三個共同達到一致性。
隔離性:事務並發執行時內部操作不能互相干擾。實現:鎖實現。
持久性:事務一旦提交,對數據庫的影響應該是永久的。實現:redolog實現。

Innodb引擎的日誌

1、undolog
作用:保存歷史快照。
目的:實現原子性,在MVCC中也起到了一定的作用。
2、redolog
作用:預寫日誌。
目的:為了高效實現持久性,將數據持久化。
詳解:首先,在執行數據更新時,效率是低的,目前幾乎所有軟件硬件的瓶頸,都卡在了IO層面。IO有兩種:順序讀寫隨機讀寫。那明明是解釋redolog的,怎麼就說起了IO呢?這要先明確一點,就是順序讀寫與隨機讀寫的效率是誰快誰慢的問題。順序讀寫:顧名思義,就是順序向尾部一次添加數據,屬於append的操作,不需要查找。隨機讀寫:需要指針的移動查找。所以很明顯,順序讀寫的效率是要遠高於隨機讀寫的。舉個例子,比如在一家飯店,中午有人來吃飯,這家店允許賒賬,所以很多人會選擇賒賬,而店主呢,就把所有的賒賬信息記錄在一本厚厚的本子里,假設裏面記了一萬條賒賬信息。好了,中午最繁忙的時候,所有員工都在忙着工作,如果有人要賒賬,怎麼辦?根本沒機會一個一個仔細找記錄。所以很自然的就會想到,我在牆上裝個黑板,或者單獨在一張紙上列出來今天的賒賬名單。等不忙了,我再慢慢地把名單記錄到本子里。所以,現在已經很明顯了,redolog就是順序讀寫,它快速記錄數據,然後數據再由順序讀寫區域慢慢向隨機讀寫區域轉換。這就是WAL:預寫日誌
redolog是Innodb引擎的日誌,而MySQLserver中自帶的日誌中有binlog,所以它倆是共存的,所以在運行時,必須保證一致性,因為binlog是MySQL主從複製時向從機同步信息的,如果不寫binlog,那麼從機會出問題,但是應該先寫哪一個呢?先寫哪個都會出問題!一旦寫第一個時斷電,就會造成主機與從機不一致。解決的方法是給redolog加個狀態,當redolog寫完數據後,給它狀態為prepare,然後去寫binlog,當binlog寫完後,再把redolog的狀態從prepare改為commit。這就是兩階段提交

MVCC

宏觀來看,MVCC就是解決事務的隔離性的問題的。

MySQL的並發場景

MySQL的並發場景有三種:讀讀讀寫寫寫
讀讀:不存在數據安全問題。
讀寫:存在數據安全問題。如臟讀、不可重複讀、幻讀。
寫寫:存在數據安全問題。如丟失更新。
而MVCC則是以不加鎖的方式解決讀寫衝突問題。

MVCC的組成部分

表的隱藏字段:數據庫表在定義時除了自己聲明的字段,還會包括一些隱藏字段,包括有:
DB_TRX_ID :最近修改事務ID。指創建這條記錄或最後一次修改這條記錄的事務id。
DB_ROLL_PTR :回滾指針。指向記錄的上一個版本,即指向undolog首條記錄。
DB_ROW_ID :隱藏主鍵。如果表沒有主鍵,自動生成一個6位元組的row id。

readview

快照讀時會產生readview,即select時會產生readview。

舉個demo:

當select時,產生了快照讀,此時生成的readview可以看到。
而如何看能否讀取到值呢?MySQL有可見性算法。

再舉一個demo

對比一下,如果按照我們的判斷,可以發現藍色的readview與第一個demo中的readview一模一樣。但是問題在於,藍色的select是不能讀取到修改之後的數據的。這當然是因為藍色的readview是錯誤的,它真正的readview其實就是黃色的readview,也就是下圖

結論

所以,隔離級別解決數據不可重複讀的關鍵,在於MVCC生成readview的機制或者時機,如果是讀已提交級別RC,每次快照讀都會生成readview,所以會產生不可重複讀的問題。如果是可重複讀隔離級別RR,只會在第一次快照讀時產生readview,不會有不可重複讀的問題。
至此,MVCC結束
至於幻讀問題,則需要加鎖來解決,幻讀產生的根本原因在於:當前讀和快照讀一起使用!如果一個事務中只有快照讀,那麼永遠不會出現幻讀問題。