MySQL面試系列:MVCC是怎麼實現的?(三)

前言

上篇文章SQL查詢語句是如何執行的提到了日誌文件:binlog、redo log 、undo log,那接下來我們接着詳細聊聊undo log和MVCC是怎麼實現的。

一、事務隔離級別

先了解一個概念:隔離級別

事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交(read-uncommitted)
讀已提交(read-committed)
可重複讀(repeatable-read)
串行化(serializable)
  • 讀未提交:能夠隨意讀取其他事務未提交的數據和已提交的數據。形象解釋:所有的數據在一個屋子裏面,任何事務修改對於其他事務都是可見的。
  • 讀已提交:不能讀取其他事務未提交的數據,但是能讀到其他事務已提交的數據。形象解釋:一個屋子裏有了一個小隔間,事務沒提交的時候數據都在小隔間,一但事務提交了,就把數據放到大廳中,其他事務都可見了。
  • 可重複讀(默認設置):其他事務提交了數據也不可見,每次查詢結果都是一致的。形象解釋:每個事務都在自己的小隔間操作,其他事務提交了數據,放到了大廳,自己也是不知道的。
  • 串行化:所有事務串行執行。

隔離級別
你是否也想問這樣一個:MySQL的事務隔離級別是怎麼實現的呢?這就是我們接下來要聊的重要話題。

什麼是幻讀
當同一查詢在不同時間產生不同的行集時,在事務內就會發生 所謂的幻象問題。例如,如果事務A SELECT執行兩次,但是第二次返回的行卻不是第一次返回,則該行是“ phantom ”行。
怎麼解決
To prevent phantoms, InnoDB uses an algorithm called next-key locking that combines index-row locking with gap locking. (官方文檔)
翻譯:next-key lock 即行鎖間隙鎖解決了幻讀問題,(index-row lock with gap lock = next-key lock)

二、Undo log

Undo log是InnoDB MVCC事務特性的重要組成部分,也是數據回滾依賴的對象。

主備工作
新建一個表:

mysql> create table user(id int primary key, name varchar, age int); 

插入一行數據:

mysql> insert into user values("1","張三",30);

在數據庫中實際存儲的時候它還會有3個隱藏的字段:DB_TRX_ID(事務ID ) 、DB_ROLL_PTR(回滾指針)、DB_ROW_ID(主鍵ID)

字段 作用
DB_TRX_ID 表示插入或更新該行的最後一個事務的事務標識符
DB_ROLL_PTR 表示指向該行回滾段(rollback segment)的指針,InnoDB 便是通過這個指針找到之前版本的數據。該行記錄上所有舊版本,在 undo 中都通過鏈表的形式組織。
DB_ROW_ID 行標識(該行ID隨着插入新行而單調增加),如果表沒有主鍵且沒有唯一索引,InnoDB 會自動生成一個隱藏主鍵。 【思考爲什麼要有這樣一個字段

每條記錄的頭信息(record header)裏都有一個專門的bit(deleted_flag)來表示當前記錄是否已經被刪除,即delete數據的時候會用deleted_flag來標記該數據已刪除。(不用深究,記住有這麼一個字段就行)

如果我們定義了主鍵(PRIMARY KEY),那麼InnoDB會選擇主鍵作爲聚集索引、如果沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的唯一索引作爲主鍵索引、如果也沒有這樣的唯一索引,則InnoDB會選擇內置6字節長的ROWID作爲隱含的聚集索引。

用一張圖來看看插入一條數據之後的樣子:
在這裏插入圖片描述
再有更新語句的時候,我們來看看undo log中的存儲情況:
在這裏插入圖片描述

在每次修改聚集索引頁上的記錄時,變更之前的記錄都會寫到undo日誌中。可以根據回滾段指針找到最近一次修改之前的undo記錄,而每條Undo記錄又能再次找到之前的變更。

回滾操作也比較簡單,讀取老版本記錄,做逆向操作即可:
對於刪除操作,清理標記刪除標記;
對於更新操作,將數據回滾到最老版本;
對於插入操作,直接刪除聚集索引和二級索引記錄。

三、MVCC是什麼?怎麼實現的?

英文全稱爲Multi-Version Concurrency Control,翻譯爲中文即 多版本併發控制。
InnoDB有兩個非常重要的模塊來實現MVCC,一個是undo日誌,用於記錄數據的變化軌跡,用於數據回滾,另外一個是Read View,用於判斷一個session對哪些數據可見,哪些不可見。

Read View:它用於控制數據的可見性
在InnoDB中,只有讀查詢纔會去構建ReadView視圖,對於類似DML這樣的數據更改,無需判斷可見性,而是單純的發現事務鎖衝突,直接堵塞操作。

ReadView包含幾個重要的變量用於判斷記錄的可見範圍
ReadView::id 創建該視圖的事務ID;
ReadView::m_ids 創建ReadView時,活躍的讀寫事務ID數組,有序存儲;(“活躍”指的就是啓動了,但還未提交)
ReadView::m_low_limit_id 設置爲當前最大事務ID;
ReadView::m_up_limit_id m_ids集合中的最小值,如果m_ids集合爲空,表示當前沒有活躍讀寫事務,則設置爲當前最大事務ID。

可見性判斷邏輯:

  • 如果記錄的trx_id小於ReadView::m_up_limit_id,則說明該事務在創建ReadView時已經提交了,肯定可見;
  • 如果記錄的trx_id大於等於ReadView::m_low_limit_id,則說明該事務是創建readview之後開啓的,肯定不可見;
  • 當trx_id在m_up_limit_id和m_low_limit_id之間時,如果在ReadView::m_ids數組中,說明創建readview時該事務是活躍的,其做的變更對當前視圖不可見,否則對該trx_id的變更可見。

通過上面的判斷,該數據變更還是不可見時,就嘗試通過undo去構建老版本記錄,直到找到可見的記錄,或者到達undo鏈表頭都未找到。

如果我們查詢得到的是一條二級索引記錄邏輯還稍微有點區別,我覺得你能把上面的東西說出來就可以了,這點細節估計一面面試官自己都不知道,後面研究也不遲。

不同隔離級別下,可見性的判斷有很大的不同

  1. READ-UNCOMMITTED 在該隔離級別下會讀到未提交事務所產生的數據更改,這意味着可以讀到髒數據,因爲它根本不會去檢查可見性或是查看老版本。

  2. READ-COMMITTED 在該隔離級別下,可以在SQL級別做到一致性讀,當事務中的SQL執行完成時,ReadView被立刻釋放了,在執行下一條SQL時再重建ReadView。這意味着如果兩次查詢之間有別的事務提交了,是可以讀到不一致的數據的。

  3. REPEATABLE-READ 可重複讀和READ-COMMITTED的不同之處在於,當第一次創建ReadView後(例如事務內執行的第一條SEELCT語句),這個視圖就會一直維持到事務結束。也就是說,在事務執行期間的可見性判斷不會發生變化,從而實現了事務內的可重複讀。

  4. SERIALIZABLE 序列化的隔離是最高等級的隔離級別,當一個事務在對某個表做記錄變更操作時,另外一個查詢操作就會被該操作堵塞住。同樣的,如果某個只讀事務開啓並查詢了某些記錄,那麼另外一個session對這些記錄的更改操作是被堵塞的。

看完這些我們就可以來說說InnoDB ACID

  • Atomicity (原子性)
    所謂原子性,就是一個事務要麼全部完成變更,要麼全部失敗。(undo log)
  • Consistency (一致性)
    一致性指的是數據庫需要總是保持一致的狀態,即使實例崩潰了,也要能保證數據的一致性。
  • Isolation (隔離性)
    隔離性是指多個事務不可以對相同數據同時做修改,事務查看的數據要麼就是修改之前的數據,要麼就是修改之後的數據。(四種隔離級別)
  • Durability(持久性)
    當一個事務完成了,它所做的變更應該持久化到磁盤上,永不丟失。(redo log)

啓動事務時begin/start transaction,執行第一條SQL語句纔會創建一個視圖 ReadView

總結

Innodb使用一種稱做ReadView(視圖)的對象來判斷事務的可見性(也就是ACID中的隔離性)。 Innodb在執行一個SELECT時會創建一個視圖對象。
又通過undo log的版本鏈去查詢可見數據。
對於RR隔離級別,視圖的生命週期到事務提交結束,對於RC隔離級別,則每條查詢開始時重分配事務。

在 REPEATABLE-READ隔離級別還能START TRANSACTION WITH CONSISTENT SNAPSHOT 顯式開啓(可忽略)

本章所有的知識點是必須掌握的,並且自己需要理一下怎麼簡短的回答面試的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章