MySQL是如何實現可重複讀的

2022-07-30 15:45:10 字數 3383 閱讀 9185

photo by picography.co

《mysql實戰45講》筆記。

可重複讀是指:乙個事務執行過程中看到的資料,總是跟這個事務在啟動時看到的資料是一致的。

我們可以簡單理解為:在可重複讀隔離級別下,事務在啟動的時候就」拍了個快照「。注意,這個快照是基於整個庫的。

這時,你可能就會想,如果乙個庫有 100g,那麼我啟動乙個事務,mysql就要拷貝 100g 的資料出來,這個過程得多慢啊。可是,我平時的事務執行起來很快啊。

實際上,我們並不需要拷貝出這 100g 的資料。我們來看下」快照「是怎麼實現的。

innodb 裡面每個事務都有乙個唯一的事務 id,叫作 transaction id。它在事務開始的時候向 innodb 的事務系統申請的,是按申請順序嚴格遞增的。

每條記錄在更新的時候都會同時記錄一條 undo log,這條 log 就會記錄上當前事務的 transaction id,記為 row trx_id。記錄上的最新值,通過回滾操作,都可以得到前乙個狀態的值。

如下圖所示,一行記錄被多個事務更新之後,最新值為 k=22。假設事務a在 trx_id=15 這個事務提交後啟動,事務a 要讀取該行時,就通過 undo log,計算出該事務啟動瞬間該行的值為 k=10。

在可重複讀隔離級別下,乙個事務在啟動時,innodb 會為事務構造乙個陣列,用來儲存這個事務啟動瞬間,當前正在」活躍「的所有事務id。」活躍「指的是,啟動了但還沒提交。

陣列裡面事務 id 為最小值記為低水位,當前系統裡面已經建立過的事務 id 的最大值加 1 記為高水位。

這個檢視陣列和高水位,就組成了當前事務的一致性檢視(read-view)。

這個檢視陣列把所有的 row trx_id 分成了幾種不同的情況。

如果 trx_id 小於低水位,表示這個版本在事務啟動前已經提交,可見;

如果 trx_id 大於高水為,表示這個版本在事務啟動後生成,不可見;

如果 trx_id 大於低水位,小於高水位,分為兩種情況:

若 trx_id 在陣列中,表示這個版本在事務啟動時還未提交,不可見;

若 trx_id 不在陣列中,表示這個版本在事務啟動時已經提交,可見。

innodb 就是利用 undo log 和 trx_id 的配合,實現了事務啟動瞬間」秒級建立快照「的能力。

初始化語句

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);

下表為事務a, b, c 的執行流程

事務a事務b

事務cstart transaction with consistent snapshot;

start transaction with consistent snapshot;

update t set k=k+1 where id=1;

update t set k=k+1 where id=1;

select k from t where id=1;

select k from t where id=1;

commit;

commit;

我們假設事務a, b, c 的 trx_id 分別為 100, 101, 102。事務a開始前活躍的事務 id 只有 99,並且 id=1 這一行資料的 trx_id=90。

根據假設,我們得出事務啟動瞬間的檢視陣列:事務a:[99, 100],事務b:[99, 100, 101],事務c:[99, 100, 101, 102]。

事務c通過更新語句,把 k 更新為 2,此時trx_id=102;

事務b通過更新語句,把 k 更新為 3,此時trx_id=101;

事務b通過查詢語句,查詢到最新一條記錄為3,trx_id=101,滿足隔離條件,返回 k=3;

事務a通過查詢語句:

查詢到最新一條記錄為3,trx_id=101,比高水位大,不可見;

通過 undo log,找到上乙個歷史版本,trx_id=102,比高水位大,不可見;

繼續找上乙個歷史版本,trx_id=90,比低水位小,可見。

我們假設事務b在更新的看不到事務c的修改,是什麼個情況?

事務b查詢到最新一條記錄為2,trx_id=102,比高水位大,不可見;

通過 undo log,找到上乙個版本,trx_id=90,比低水位小,可見;

返回記錄 k=1,執行 k=k+1,把 k 更新為2,此時 trx_id=101。

如果是這種情況,事務c可能就蒙了:「啥子情況,我的更新怎麼就丟了」。事務b覆蓋了事務c的更新。

所以,innodb在更新時運用一條規則:更新資料都是先讀後寫的,而這個讀,只能讀當前的值,稱為「當前讀「 (current read)。

因此,事務b在更新時要拿到最新的資料,在此基礎上做更新。緊接著,事務b在讀取的時候,查詢到最新的記錄為3, trx_id=101 為當前事務id,可見。

我們再假設另一種情況:

事務b在更新之後,事務c緊接著更新,事務b回滾了,事務c成功提交。

事務b事務c

start transaction with consistent snapshot;

start transaction with consistent snapshot;

update t set k=k+1 where id=1;

update t set k=k+1 where id=1;

select k from t where id=1;

rollback;

commit;

如果按照當前讀的定義,會發生以下事故,假設當前 k=1:

事務b把 k 更新為 2;

事務c讀取到當前最新值,k=2,更新為3;

事務b回滾;

事務c提交。

這時候,事務c發現自己想要執行的是 +1 操作,結果變成了 」+2「 操作。

innodb 肯定不允許這種情況的發生,事務b在執行更新語句時,會給該行加上行鎖,直到事務b結束,才會釋放這個鎖。

innodb 的行資料有多個版本,每個版本都有 row trx_id。

事務根據 undo log 和 trx_id 構建出滿足當前隔離級別的一致性檢視。

可重複讀的核心是一致性讀,而事務更新資料的時候,只能使用當前讀,如果當前記錄的行鎖被其他事務占用,就需要進入鎖等待。

03 | 事務隔離:為什麼你改了我還看不見?-極客時間

08 | 事務到底是隔離的還是不隔離的?-極客時間

作者 張小超

mysql 可重複讀。

一 可重複讀 我們先看看現象,再分析原理。我的mysql版本是5.5。下面是一張表,只有一條資料,並且我開啟了事物 此時,另乙個事物將record加1,因此我在開啟乙個命令列客戶端,執行下面的命令 成功加1之後,實際上,資料庫中record肯定是2。然後回到之前的客戶端,再查一次 沒毛病,recor...

mysql可重複讀

mysql innodb的預設隔離級別是可重複讀,之前理解有些偏差,查閱一些資料後總結出幾點 首先有兩個概念 一致性檢視 當乙個事務開啟時,innodb會生成乙個檢視,這個檢視是邏輯檢視,通過undo log和row tranzaction id控制實現。在該事務的任何時間點,一致性檢視中的資料都是...

MySQL 的可重複讀

我在這裡分享一篇關於 mysql 的可重複讀介紹,講得挺好的,可以解決一些疑惑,鏈結在下方引用處。sql 1992 年標準關於幻讀 phantom 的解釋 乙個事務 t1 根據某些查詢條件 讀取某幾行資料,然後事務 t2 執行 sql 語句插入一行或多行滿足查詢條件 的資料 這時候如果事務 t1 重...