總結自小林coding,bojiangzhou
臟讀、不可重復讀、幻讀
說的都是并發讀取的問題,最簡單的方式就是給記錄加一把鎖,不管是更新、讀取記錄都需要競爭到這把鎖之后才能操作。但這種方式的并發性能可想而知會有多么低。
于是 InnoDB 就設計了MVCC
來解決并發讀取的問題,MVCC
就是多版本并發控制(Multi-Version Concurrency Control
)。在 RC
、RR
這兩種隔離級別下執行SELECT
查詢時,通過訪問記錄的版本鏈
,而不需要加鎖,這樣使得不同事務的讀-寫
操作可以并發執行,從而提升數據庫的性能。
MVCC需要依賴undo log版本鏈:
-
對于使用
RU
隔離級別的事務來說,由于可以讀到未提交事務修改過的記錄,所以直接讀取記錄的最新版本就好了。 -
對于使用
RC
、RR
隔離級別的事務來說,都必須保證讀到已提交
事務修改過的記錄,如果另一個事務修改的記錄還未提交,是不能直接讀取記錄的最新版本的,此時就可以沿著undo版本鏈查找當前事務可見的版本。
ReadView
那如何判斷版本鏈上的哪個版本是當前事務可見的呢?
InnoDB 設計了一個 ReadView
,在執行一個事務的時候就會創建一個ReadView
。ReadView 有四個關鍵屬性:
-
m_ids :指的是在創建 Read View 時,當前數據庫中「活躍事務」的事務 id 列表,注意是一個列表,“活躍事務”指的就是,啟動了但還沒提交的事務。
-
min_trx_id :指的是在創建 Read View 時,當前數據庫中「活躍事務」中事務 id 最小的事務,也就是 m_ids 的最小值。
-
max_trx_id :這個并不是 m_ids 的最大值,而是創建 Read View 時當前數據庫中應該給下一個事務的 id 值,也就是全局事務ID(
Max Trx Id
); -
creator_trx_id :指的是創建該 Read View 的事務的事務 id。事務中只有在執行了增刪改操作時才會分配一個事務ID,如果是一個只讀事務,那 creator_trx_id 默認就為
0
。
MVCC 流程
undo log 中的隱藏列 trx_id
表示產生這條 undo log 時的事務的事務ID。判斷此版本是否可訪問的依據就是用 undo log 中的 trx_id
屬性值與 ReadView 中的各個屬性做比較。
通過如下步驟來判斷版本是否可被訪問:
-
① 如果
trx_id
等于creator_trx_id
,說明當前事務在訪問它自己修改過的記錄,所以該版本記錄可以被當前事務訪問。(可以自己訪問自己的事務) -
② 如果
trx_id
小于min_trx_id
,說明生成該版本記錄的事務在當前事務生成 ReadView 前已經提交,所以該版本記錄可以被當前事務訪問。(可以訪問已經提交的事務) -
③ 如果
trx_id
大于或等于max_trx_id
,說明生成該版本記錄的事務在當前事務生成 ReadView 后才開啟,所以該版本記錄不可以被當前事務訪問。(不能訪問“未來”的事務) -
④ 如果
trx_id
在min_trx_id
和max_trx_id
之間,此時再判斷一下trx_id
是不是在m_ids
列表中,如果在,說明創建 ReadView 時生成該版本記錄的事務還是活躍的,該版本記錄不可以被訪問(不能訪問同期未提交的事務);如果不在,說明創建 ReadView 時生成該版本記錄的事務已經被提交,該版本記錄可以被訪問。(可以訪問同期已提交的事務)
RC 和 RR
READ COMMITTED
和 REPEATABLE READ
隔離級別的區別就是它們生成ReadView
的時機不同。
-
READ COMMITTED
是每次查詢前都會生成一個獨立的 ReadView。 -
REPEATABLE READ
則只在第一次查詢前生成一個 ReadView,之后的查詢都重復使用這個 ReadView。 -
READ UNCOMMITTED
則不需要生成 ReadView,直接讀取行記錄的數據。
快照讀和當前讀
簡單的SELECT
查詢,是讀取undo版本鏈上的一個快照版本,可以稱為快照讀
或一致性非鎖定讀
。由于是讀取的快照,因此在RR
隔離級別下可以避免幻讀的發生。
但如果是INSERT、DELETE、UPDATE
語句,例如下面的SQL,這個 UPDATE 語句會更新 balance=0 的記錄,這種方式就稱為當前讀
,讀取的是最新的數據。當前讀
能讀取到別的事務已提交的修改,就可能會產生幻讀的問題。UPDATE account SET balance=100 WHERE balance = 0;
而對于幻讀現象,不建議將隔離級別升級為串行化,因為這會導致數據庫并發時性能很差。MySQL InnoDB 引擎的默認隔離級別雖然是「可重復讀」,但是它很大程度上避免幻讀現象,解決的方案有兩種:
-
針對快照讀(普通 select 語句),是通過 MVCC 方式解決了幻讀,因為可重復讀隔離級別下,事務執行過程中看到的數據,一直跟這個事務啟動時看到的數據是一致的,即使中途有其他事務插入了一條數據,是查詢不出來這條數據的,所以就很好了避免幻讀問題。
-
針對當前讀(select ... for update 等語句),是通過 next-key lock(記錄鎖+間隙鎖)方式解決了幻讀,因為當執行 select ... for update 語句的時候,會加上 next-key lock,如果有其他事務在 next-key lock 鎖范圍內插入了一條記錄,那么這個插入語句就會被阻塞,無法成功插入,所以就很好了避免幻讀問題。