MySQL的MVCC机制看完这篇你还不懂,算我输
前言
MySQL中大名鼎鼎的MVCC機制想必大家都有所耳聞吧,雖然在平時MySQL使用過程中基本上用不到,但是面試中出場率十分高,而且作為架構師的你也是需要知道它的工作機制。那么你對MVCC機制了解多少呢?MVCC機制是用來干嘛的呢?底層的工作原理是怎么樣的呢?本文就帶你一探究竟。
MVCC機制是什么?
MVCC,英文全稱Multiversion Concurrency Control,多版本并發控制。簡單理解,就是相當于給我們的MySQL數據庫拍個“快照”,定格某個時刻數據庫的狀態。
那你可能問為什么要拍個“快照”,也就是MVCC機制?
還記得事務的一大特性就是隔離性,一共有4個隔離級別,讀未提交,讀已提交,可重復讀,串行化。
?以MySQL InnoDB 引擎的默認隔離級別可重復讀為例,可重復讀指一個事務執行過程中看到的數據,一直跟這個事務啟動時看到的數據是一致的。
關于事務的基本特性請移步一文帶你理解MySQL事務核心知識點
為了保證事務啟動到結束整個生命周期看到的數據是一致的, 一般有兩種方案:
MySQL對數據“讀-寫”的時候,加鎖,其他事務寫這條數據時加上鎖,其他事務讀取的時候阻塞。
MySQL可以對事務啟動的時候,對數據庫拍個“快照”,那么事務運行過程中讀取都從這個快照讀取,不也是保證數據一致么。
第一種方案存在明顯的問題,加鎖會引發阻塞,從而降低數據庫性能。而MySQL設計者們采用第二種,也就是大名鼎鼎的MVCC,它不僅能夠解決不可重復讀,還一定程度解決幻讀的問題,因為你整個數據庫快照都有了,你就知道那個時刻的數據了。
雖然說SQL標準定義中可重復讀隔離級別下會存在幻讀的現象,但是不同的數據庫廠商可以基于SQL標準下有不同的實現,那么不同隔離級別下發生的現象也會有出入,就拿MySQL的可重復讀隔離級別就可以一定程度保證幻讀。
小結一下:
MVCC在MySQL InnoDB中的實現主要是為了提高數據庫并發性能,用更好的方式去處理讀-寫沖突 ,做到即使有讀寫沖突時,也能做到不加鎖 , 非阻塞并發讀,而這個讀指的就是快照讀 , 而非當前讀。
什么是快照讀和當前讀?
前面提到了快照讀和當前讀,這又有什么不一樣呢,什么樣的sql語句算是快照讀,什么樣的又算是當前讀呢?
快照讀
快照讀又叫普通讀,也就是利用MVCC機制讀取快照中的數據。不加鎖的簡單的SELECT 都屬于快照讀,比如這樣:
SELECT * FROM user WHERE ... 復制代碼-
快照讀是基于MVCC實現的,提高了并發的性能,降低開銷
-
大部分業務代碼中的讀取都屬于快照讀
當前讀
當前讀讀取的是記錄的最新版本,讀取時會對讀取的記錄進行加鎖, 其他事務就有可能阻塞。加鎖的 SELECT,或者對數據進行增刪改都會進行當前讀。比如:
SELECT * FROM user LOCK IN SHARE MODE; # 共享鎖 SELECT * FROM user FOR UPDATE; # 排他鎖 INSERT INTO user values ... # 排他鎖 DELETE FROM user WHERE ... # 排他鎖 UPDATE user SET ... # 排他鎖 復制代碼-
update、delete、insert語句雖然沒有select, 但是它們也會先進行讀取,而且只能讀取最新版本。
MVCC機制是咋工作的呢?
前面打個比方說MVCC機制相當于是基于整個數據庫“拍了個快照”,這時,你會說這看上去不太現實啊。如果一個庫有 100G,那么我啟動一個事務,MySQL 就要保存 100G 的數據出來,這個過程得多慢啊,而且也很占用空間啊,根本就不能支持幾個事務啊。別急,我們現在來講解下MVCC機制是如何工作的。
數據的多個版本
首先MySQL innoDB存儲引擎需要支持一條數據可以保留多個歷史版本。怎么保留呢?還記得事務日志undo log嗎?
undo log保存了數據的各個歷史版本,用于數據的回滾,保證事務的一致性。詳情查看詳解MySQL事務日志——undo log
對于使用 InnoDB 存儲引擎的數據庫表,它的聚簇索引記錄中都包含下面兩個隱藏列:
-
trx_id,當一個事務對某條聚簇索引記錄進行改動時,就會把該事務的事務 id 記錄在 trx_id 隱藏列里;
-
roll_pointer,每次對某條聚簇索引記錄進行改動時,都會把舊版本的記錄寫入到 undo 日志中,然后這個隱藏列是個指針,指向每一個舊版本記錄,于是就可以通過它找到修改前的記錄。
InnoDB 里面每個事務有一個唯一的事務 ID,叫作 transaction id。它是在事務開始的時候向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的。
?如上圖所示,針對id=1的這條數據,都會將舊值放到一條undo日志中,就算是該記錄的一個舊版本,隨著更新次數的增多,所有的版本都會被 roll_pointer 屬性連接成一個鏈表,我們把這個鏈表稱之為版本鏈,根據版本鏈就可以找到這條數據歷史的版本。
一致性視圖ReadView
利用undo log日志我們已經保留下了數據的各個版本,那么現在關鍵的問題是要讀取哪個版本的數據呢?
這時就需要用到ReadView了,ReadView就是事務在使用MVCC機制進行快照讀操作時產生的一致性視圖, 比如針對可重復讀隔離級別,是在事務啟動的時候,創建一個ReadView, 那ReadView種都有哪些關鍵信息呢?
-
trx_ids: 指的是在創建 ReadView 時,當前數據庫中「活躍事務」的事務 id 列表,注意是一個列表, “活躍事務”指的就是,啟動了但還沒提交的事務。
-
min_trx_id: 指的是在創建 ReadView 時,當前數據庫中「活躍事務」中事務 id 最小的事務,也就是 m_ids 的最小值。
-
max_trx_id:這個并不是 m_ids 的最大值,而是創建 ReadView 時當前數據庫中應該給下一個事務的 id 值,也就是全局事務中最大的事務 id 值 + 1;
-
creator_trx_id :指的是創建該 ReadView 的事務的事務 id, 只有在對表中的記錄做改動時(執行INSERT、DELETE、UPDATE這些語句時)才會為 事務分配事務id,否則在一個只讀事務中的事務id值都默認為0。
?對于當前事務的啟動瞬間來說,讀取的一個數據版本的trx_id,有以下幾種可能:
-
如果被訪問版本的trx_id屬性值與ReadView中的 creator_trx_id 值相同,意味著當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。
-
如果落在綠色部分,表示這個版本是已提交的事務或者是當前事務自己生成的,這個數據是可見的;
-
如果落在紅色部分,表示這個版本是由將來啟動的事務生成的,是肯定不可見的;
-
如果落在黃色部分,那就包括兩種情況
-
若 數據的trx_id在trx_ids數組中,表示這個版本是由還沒提交的事務生成的,不可見, 去讀取這條數據的歷史版本,這條數據的歷史版本中都包含了事務id信息,去查找第一個不在活躍事務數組的版本記錄。 若 數據的trx_id不在trx_ids數組中,表示這個版本是已經提交了的事務生成的,可見。
這種通過版本鏈 + 一致性視圖 來控制并發事務訪問同一個記錄時的行為就叫 MVCC(多版本并發控制),現在你明白MySQL如何實現了“秒級創建快照”的能力了吧。
還是不懂?舉例說明
如果你對MVCC機制的整個流程還是比較模糊,我們現在舉例來說明下。
比如student表中有一個事務id為8的插入記錄:
insert into student(id, name, class) values(1, '張三', '一班') 復制代碼我們現在在MySQL的讀已提交和可重復讀隔離級別下,MVCC機制的整個工作流程。
MySQL中的讀未提交和序列化并不需要MVCC機制,讀未提交,直接讀取別人未提交的數據,而序列化全程用加鎖的方式,也用不上MVCC, 大家體會下。
可重復讀隔離級別下
可重復讀REPEATABLE READ 隔離級別的事務來說,只會在第一次執行查詢語句時生成一個 ReadView ,之后的查詢就不會重復生成了。
begin/start transaction 命令并不是一個事務的起點,在執行到它們之后的第一個操作 InnoDB 表的語句,事務才真正啟動。如果你想要馬上啟動一個事務,可以使用 start transaction with consistent snapshot 這個命令。
| 事務10 | 事務20 | 事務30 |
| beginUPDATE student SET name="李四" WHERE id=1;UPDATE student SET name="王五" WHERE id=1; | ||
| begin更新了一些其他表的數據 | ||
| beginSELECT * FROM student WHERE id = 1; |
?事務10和20均為提交,現在事務30執行select, 那么得到的結果是什么呢?
在執行select語句時會先生成一個ReadView,ReadView的trx_ids列表的內容就是[10, 20],min_trx_id為10,max_trx_id為21,creator_trx_id為0。
然后從版本鏈中挑選可見的記錄,從圖中看出,最新版本的列name的內容是'王五',該版本的trx_id值為10,在trx_ids列表內,所以不符合可見性要求,根據roll_pointer跳到下一個版本。
下一個版本的列name的內容是'李四',該版本的trx_id值也為10,也在trx_ids列表內,所以也不符合要求,繼續跳到下一個版本。
下一個版本的列name的內容是'張三',該版本的trx_id值為8,小于ReadView中的min_trx_id值10,說明已經提交了,那么最終返回'張三'。
讀已提交隔離級別下
讀已提交READ COMMITTED是每次讀取數據前都生成一個ReadView。基本的規則和流程與可重復讀隔離級別一致,這里不做重復贅敘。
總結
本問重點介紹了MVCC機制,以及 MVCC 在 READ COMMITTD、 REPEATABLE READ 這兩種隔離級別的事務在執行快照讀操作時訪問記錄的版本鏈的過程。這樣使不同事務的 讀-寫 、 寫-讀 操作并發執行,從而提升系統性能。
-
READ COMMITTD 在每一次進行普通SELECT操作前都會生成一個ReadView
-
REPEATABLE READ 只在第一次進行普通SELECT操作前生成一個ReadView,之后的查詢操作都重復使用這個ReadView就好了。
如果本文對你有幫助的話,請留下一個贊吧
總結
以上是生活随笔為你收集整理的MySQL的MVCC机制看完这篇你还不懂,算我输的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Quartus问题收集
- 下一篇: idea替换关键字