目錄
前言
mvcc 是如何工作的?
數據的更新
前言
mvcc 與一個事物的隔離級別有關,未提交讀永遠讀的是當前值,串行化是通過加鎖實現,這兩種隔離級別都與mvcc 沒有任何關系。只要一提到mvcc應該想到的是讀提交以及可重復讀,大家有沒有想過都是mvcc,為啥這兩個隔離級別所呈現的結果有些不一樣呢?難道會有兩套mvcc 嗎?當然不是,對于這個問題,稍后我會說明的。
我先舉一個簡單的例子。下面是一個表的初始化語句。
CREATE TABLE `t` (`id` int(11) NOT NULL,`k` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);
sessionA | sessionB | sessionA |
---|---|---|
![]() | ![]() | ![]() |
在此我說明下,sessionA 是在同一個事物下,事務的隔離級別是可重復讀,sessionB 是另外一個事物。是不是感覺很神奇,sessionB 對數據操作成功了,sessionA 沒有收到任何干擾。
是不是感覺sessionA 在begin 的時候像相機一樣為數據表拍了一張快照。而這個快照也是我們常說的一致性讀視圖即 consistent read view。它的作用是事務執行期間用來定義“我能看到什么數據”。
mvcc 是如何工作的?
在上面我們講到了快照,大家是不是感覺我在扯淡,100G的庫,你也去快照,你要拷貝100G的數據,別人不會拍死你,放心好了拍不死我的。
其實在啟動一個事物的時候,并不需要拷貝數據,只需要在事物系統申請一個事物id 叫做 transaction id,這個id 是原子遞增的,意味著一個數據庫不會出現兩個相同的事物id
每行數據也都是有多個版本的。每次事務更新數據的時候,都會生成一個新的數據版本,并且把 transaction id 賦值給這個數據版本的事務 ID,記為 row trx_id。同時,舊的數據版本要保留在undo log 里,并且在新的數據版本中,能夠有信息可以直接拿到它。
可以這么說,數據表中的一行記錄,其實可能有多個版本,每個版本都有自己的row trx_id。
按照可重復讀的定義,一個事物啟動的時候,能夠看到所有已經提交的事物結果。但是之后其他事物的更新,它卻看不見。
它是怎么找到自己能夠看見的數據呢,會拿到每個版本的 row trx_id,如果是在我啟動以后生成的row trx_id,繼續往前找,直到找到自己提交的或者是本事物啟動之前提交的。
為了方便比較,在實現上,innodb 為每個事物構建一個數組,用來保存這個事物啟動瞬間,當前正在“活躍” 的所有事物ID。“活躍” 指的是,啟動了還沒有提交的。
數組里面事務 ID 的最小值記為低水位,當前系統里面已經創建過的事務 ID 的最大值加 1 記為高水位。
這個視圖數組和高水位,就組成了當前事物的一致性視圖。
而數據版本的可見性規則,就是基于數據的 row trx_id 和這個一致性視圖的對比結果得到的。
就拿一致性視圖而言,規則是這樣的:
-
如果 row trx_id 小于這個視圖數組的最小值,說明已經提交了,那么這個數據就是可見的。
-
如果 row trx_id 大于這個高水位,肯定不可見呀,說明事物還沒有創建
-
如果 row trx_id 在 最小值和高水位之間又有兩種情況:row trx_id剛好就在視圖數組是說明還沒有提交也不可見,不在視圖數組里說明提交了就可見。
所以你現在知道了,InnoDB 利用了“所有數據都有多個版本”的這個特性,實現了“秒級創建快照”的能力。
所以一個數據版本,對于一個事務視圖來說,除了自己更新總是可見以外,有三種情況:
-
版本未提交,不可見;
-
版本已提交,但是是在視圖創建后提交的,不可見;
-
版本已提交,而且是在視圖創建前提交的,可見。
下面我在在回到最開始提到的那個問題,讀提交為啥又有些不一樣呢,讓我們在回顧一下什么是讀提交,在事務中,我們可以讀到已經提交的事物,只要在我事物中的任何一個時刻提交的,我都可以讀到。上面的規則對于讀提交同樣適用,只不過讀提交是在每個select語句都會去申請一個transaction id作為它的row trx_id,同時也會構建一個新的事物數組。
數據的更新
上面我們講到了mvcc,其實只是在select 用到,如果是update ,或者下面的語句。
select k from t where id=1 lock in share mode;
?
select k from t where id=1 for update;
大家都知道更新數據都是先讀后寫的,而這個讀,只能讀當前的值,稱為“當前讀”(current read)。
或者是select 語句加上了 share mode 共享鎖 或者 for update 拍他鎖,這些都只能用到當前讀,與mvcc 沒有一點關系了。
所以這些語句執行后,會對行進行加鎖,update 加行鎖,share mode 加的是讀鎖, for update 加的是寫鎖。讀到最新的數據
而這些鎖都是兩階段鎖,從加鎖開始到事物結束。這么做對于數據庫來說也是無賴之舉,為了保證數據的一致性,是數據的同步技術。