數據庫事務和MVCC多版本併發控制

事務特性

  • Atomicity(原子性)html

    一個事務必須被視爲一個不可分割的最小工做單位,整個事務中的全部操做要麼所有提交成功,要麼所有失敗回滾。c++

  • Consistency(一致性)sql

    數據庫老是從一個一致性狀態轉換到另外一個一致性狀態,事務執行以前和執行以後都必須處於一致性狀態。數據庫

  • Isolation(隔離性)segmentfault

    一般來講,一個事務所作的修改在最終提交以前,對其它事務是不可見的。關於事務的隔離性,數據庫提供了多種隔離級別。併發

  • Durability(持久性)性能

    一旦事務提交,則其所作的修改就會永久保存到數據庫中。即使是數據庫系統遇到故障的狀況下也不會丟失。this

併發事務的問題

  • 髒讀

一個事務正在對一條記錄進行修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態。這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。spa

時間 事務A 事務B
T1 開啓事務 開啓事務
T2 查詢帳戶餘額爲1000
T3 充值500,餘額修改成1500
T4 查詢餘額爲1500
T5 撤銷事務,餘額改回1000
T6 匯入500,餘額修改成2000
T7 提交事務
  • 不可重複讀

一個事務在讀取某些數據後的某個時間,再次讀取之前讀過的數據,卻發現其讀出的數據已經發生了變動、或者某些記錄已經被刪除了。3d

時間 事務A 事務B
T1 開啓事務 開啓事務
T2 select * from user where user_id=100 假設爲小明的用戶信息
T3 將user_id=100的用戶信息對應的年齡修改成18
T4 提交事務
T5 再次查詢發現用戶的年齡變動
T6 ...
T7 提交事務
  • 幻讀

一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其它事務插入了知足其查詢條件的新數據。

時間 事務A 事務B
T1 開啓事務 開啓事務
T2 select * from user where age=18 假設獲得兩條記錄
T3 向user表插入一條age=18的新記錄
T4 提交事務
T5 再次查詢獲得三條記錄
T6 ..
T7 提交事務

幻讀和不可重複讀的區別

  • 不可重複讀的重點是修改:在同一事務中,相同的條件,第一次和第二次讀到的數據不一致(中間有其它事務提交了修改)。
  • 幻讀的重點是新增或者刪除:在同一事務中,相同的條件,第一次和第二次讀到的記錄數不同(中間有其它事務提交了新增或者刪除)。

事務隔離級別

SQL標準定義了4類隔離級別,每一種級別都規定了一個事務中所作的修改,哪些在事務內和事務間是可見的,哪些是不可見的。

  • Read Uncommited

全部事務均可以看到其它未提交事務的執行結果,該隔離級別通常不會使用。

  • Read Committed(RC)

一個事務只能看到已經提交的事務所作的變動。

  • Repeatable Read(RR)

確保同一事務的多個實例在併發讀取數據時會看到相同的數據行。

  • Serializable

徹底串行化讀,每次讀都須要得到表級共享鎖,讀寫相互阻塞。

隔離級別 髒讀 不可重複讀 幻讀
Read Uncommited Yes Yes Yes
Read Committed No Yes Yes
Repeatable Read No No Yes
Serializable No No No

併發事務解決方案

髒讀、不可重複讀和幻讀都是數據庫讀一致性問題,須要由數據庫提供必定的事務隔離機制來解決。

(1)鎖機制

解決寫-寫衝突問題。在讀取數據前,對其加鎖,防止其它事務對該數據進行修改。

  • 悲觀鎖

    每每依靠數據庫提供的鎖機制。

  • 樂觀鎖

    大可能是基於數據版本記錄機制來實現。

(2)MVCC多版本併發控制

解決讀-寫衝突問題。不用加鎖,經過必定機制生成一個數據請求時間點時的一致性數據快照, 並用這個快照來提供必定級別 (語句級或事務級) 的一致性讀取。這樣在讀操做的時候不須要阻塞寫操做,寫操做時不須要阻塞讀操做。

MVCC多版本併發控制

Mysql的大多數事務型存儲引擎實現都不是簡單的行級鎖,基於併發性能考慮,通常都實現了MVCC多版本併發控制。MVCC是經過保存數據在某個時間點的快照來實現的。無論事務執行多長時間,事務看到的數據都是一致的。

讀操做

讀操做分紅兩類:快照讀和當前讀。

快照讀:簡單的select操做屬於快照讀,不加鎖。

  • select * from table where ? ;

當前讀:特殊的讀操做,插入/更新/刪除操做,屬於當前讀,須要加鎖。

  • select * from table where ? lock in share mode ;
  • select * from table where ? for update ;
  • update table set ? where ? ;
  • delete from table where ? ;

數據存儲

innodb存儲引擎中,每行數據都包含了一些隱藏字段:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR和DELETE_BIT。

數據庫存儲結構

  • DB_TRX_ID:用來標識最近一次對本行記錄作修改的事務的標識符,即最後一次修改本行記錄的事務id。delete操做在內部來看是一次update操做,更新行中的刪除標識位DELELE_BIT。
  • DB_ROLL_PTR:指向當前數據的undo log記錄,回滾數據經過這個指針來尋找記錄被更新以前的內容信息。
  • DB_ROW_ID:包含一個隨着新行插入而單調遞增的行ID, 當由innodb自動產生彙集索引時,彙集索引會包括這個行ID的值,不然這個行ID不會出如今任何索引中。
  • DELELE_BIT:用於標識該記錄是否被刪除。

數據操做

  • insert

    建立一條記錄,DB_TRX_ID爲當前事務ID,DB_ROLL_PTR爲NULL。

  • delete

    將當前行的DB_TRX_ID設置爲當前事務ID,DELELE_BIT設置爲1。

  • update 複製一行,新行的DB_TRX_ID爲當前事務ID,DB_ROLL_PTR指向上個版本的記錄,事務提交後DB_ROLL_PTR設置爲NULL。

  • select

    一、只查找建立早於當前事務ID的記錄,確保當前事務讀取到的行都是事務以前就已經存在的,或者是由當前事務建立或修改的;

    二、行的DELETE BIT爲1時,查找刪除晚於當前事務ID的記錄,確保當前事務開始以前,行沒有被刪除。

一致性讀

Mysql的一致性讀是經過read view結構來實現。 read view主要是用來作可見性判斷的,它維護的是本事務不可見的當前其餘活躍事務。其中最先的事務ID爲up_limit_id,最遲的事務ID爲low_limit_id

trx_id_t	low_limit_id;
				/*!< The read should not see any transaction with trx id >= this value. In other words, this is the "high water mark". */
	trx_id_t	up_limit_id;
				/*!< The read should see all trx ids which are strictly smaller (<) than this value. In other words, this is the "low water mark". */
複製代碼

如何理解low_limit_id

能夠參考知乎這個答案來理解。low_limit_id應該是當前系統還沒有分配的下一個事務ID(從這個語義來更容易理解),也就是目前已經出現過的事務ID的最大值+1

MySQL 在 RC 隔離級別下是如何實現讀不阻塞的? 呵呵一笑百媚生的答案

low_limit_id理解

可見性判斷

假設要讀取的行的最後提交事務id(即當前數據行的穩定事務id)爲 trx_id,可見性比較過程以下:

  1. trx_id < up_limit_id => 此記錄的最後一次修改在read view建立以前,跳轉到步驟5;
  2. trx_id > low_limit_id => 此記錄的最後一次修改在read view建立以後,跳轉到步驟4;
  3. up_limit_id <= trx_id <= low_limit_id => 從up_limit_id到low_limit_id進行遍歷,若是trx_id等於他們之中的某個事務id的話,表示該記錄的最後一次修改還沒有保存,跳轉到步驟4。不然跳轉到步驟5;
  4. 今後記錄的DB_ROLL_PTR指針所指向的undo log(此記錄的上一次修改),將undo log的DB_TRX_ID賦值給trx_id,跳轉到步驟1從新開始計算可見性;
  5. 若是此記錄的DELELE_BIT爲false,說明該記錄未被刪除,能夠返回,不然不返回。

RR和RC隔離級別

Repeatable Read和Read Committed隔離級別都是基於read view來實現,不一樣之處在於:

  • Repeatable Read

    read view是在執行事務中第一條select語句的瞬間建立,後續全部的select都是複用這個對象,因此能保證每次讀取的一致性。(可重複讀的語義

  • Read Committed

    事務中每條select語句都會建立read view,這樣就能夠讀取到其它事務已經提交的內容。

對於InnoDB來講,Repeatable Read雖然比Read Committed隔離級別高,開銷反而相對較小。

參考資料

數據庫事務與MySQL事務總結

MySQL-InnoDB-MVCC多版本併發控制

MySQL InnoDB MVCC深度分析

樂觀鎖和 MVCC 的區別?

MySQL 在 RC 隔離級別下是如何實現讀不阻塞的?

相關文章
相關標籤/搜索