一、快照讀與當前讀
快照讀(SnapShot Read) 是一種一致性不加鎖的讀,是 InnoDB 併發如此之高的核心原因之一。
在 READ COMMITTED 事務隔離級別下,一致性不加鎖的讀是指,總是讀取被鎖定行的最新一份快照資料,因此其它事務修改了該行資料,該事務也能讀取到,這也貼合了 RC 隔離級別下存在幻讀的問題;
在 REPEATABLE READ 事務隔離級別下,一致性不加鎖的讀是指,事務讀取到的資料,要麼是事務開始前就已經存在的資料,要麼是事務自身插入或者修改過的資料。(下面將以此隔離級別說明);
不加鎖的簡單的 SELECT 都屬於快照讀,例如:
SELECT * FROM t WHERE id=1;
與快照讀相對應的則是當前讀(Current Read),當前讀就是讀取最新資料,而不是歷史版本的資料。加鎖的 SELECT 就屬於當前讀,例如:
SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE;SELECT * FROM t WHERE id=1 FOR UPDATE;
SELECT...FOR UPDATE 對讀取的行記錄加一個 X 鎖,其它事務不能對已鎖定的行加上任何鎖。
SELECT...LOCK IN SHARE MODE 對讀取的行記錄加一個 S 鎖,其它事務可以向被鎖定的行加 S 鎖,但是如果加 X 鎖,則會被阻塞。
二、基於快照讀的多版本併發控制多版本併發控制技術的英文全稱是:Multiversion Concurrency Control,簡稱 MVCC,是透過儲存資料的歷史版本,透過對資料行的多個版本管理來實現資料庫的併發控制。這樣我們就可以透過比較版本號決定資料是否顯示出來,讀取資料的時候不需要加鎖也可以保證事務的隔離效果(可以理解成樂觀鎖)。
多版本併發控制(MVCC)只在可重複讀(REPEATABLE READ)和提交讀(READ COMMITTED)兩個隔離級別下工作,其他兩個隔離級別都和 MVCC 不相容,因為未提交讀(READ UNCOMMITTED),總是讀取最新的資料行,而不是符合當前事務版本的資料行;而可序列化(SERIALIZABLE) 則會對所有讀取的行都加鎖。
MySQL 的大多數事務型儲存引擎實現的都不是簡單的行級鎖。基於提升併發效能的考慮,它們一般都同時實現了多版本併發控制(MVCC)。不僅是 MySQL,包括 Oracle、PostgreSQL 等其他資料庫系統也都實現了 MVCC,但各自的實現機制不盡相同,因為 MVCC 沒有一個統一的實現標準,典型的有樂觀(optimistic)併發控制和悲觀(pessimistic)併發控制。
三、多版本併發控制解決了哪些問題?1. 讀寫之間阻塞的問題透過 MVCC 可以讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就可以提升事務併發處理能力。
提高併發的演進思路:
普通鎖,只能序列執行;讀寫鎖,可以實現讀讀併發;資料多版本併發控制,可以實現讀寫併發。
2. 降低了死鎖的機率因為 InnoDB 的 MVCC 採用了樂觀鎖的方式,讀取資料時並不需要加鎖,對於寫操作,也只鎖定必要的行。
3. 解決一致性讀的問題一致性讀也被稱為快照讀,當我們查詢資料庫在某個時間點的快照時,只能看到這個時間點之前事務提交更新的結果,而不能看到這個時間點之後事務提交的更新結果。
四、InnoDB 的 MVCC 是如何工作的?1. InnoDB 是如何儲存記錄的多個版本的?事務版本號: 每開啟一個事務,我們都會從資料庫中獲得一個事務 ID(也就是事務版本號),這個事務 ID 是自增長的,透過 ID 大小,我們就可以判斷事務的時間順序。
行記錄的隱藏列: InnoDB 的葉子段儲存了資料頁,資料頁中儲存了行記錄,而在行記錄中有一些重要的隱藏欄位:
DB_ROW_ID:6-byte,隱藏的行 ID,用來生成預設聚簇索引。如果我們建立資料表的時候沒有指定聚簇索引,這時 InnoDB 就會用這個隱藏 ID 來建立聚集索引。採用聚簇索引的方式可以提升資料的查詢效率。DB_TRX_ID:6-byte,操作這個資料的事務 ID,也就是最後一個對該資料進行插入或更新的事務 ID。(InnoDB 的插入、更新、刪除都會更新該事務 ID,同時刪除會將一個特殊位標記為已刪除)DB_ROLL_PTR:7-byte,回滾指標,也就是指向這個記錄的 Undo Log 資訊。Undo Log: InnoDB 將行記錄快照儲存在了 Undo Log 裡,我們可以在回滾段中找到它們,如下圖所示,回滾指標將資料行的所有快照記錄都透過連結串列的結構串聯了起來,每個快照的記錄都儲存了當時的 db_trx_id,也是那個時間點操作這個資料的事務 ID。這樣如果我們想要找歷史快照,就可以透過遍歷回滾指標的方式進行查詢。