MySQL數據庫的事務隔離和MVCC

前言

事務是訪問數據庫的一個操做序列,數據庫應用系統經過事務集來完成對數據庫的存取.mysql

1. 什麼是事務?

事務必須服從ISO/IEC所制定的ACID原則。ACID是原子性(atomicity)、一致性(consistency)、隔離性(isolation)、持久性(durability)的縮寫,這四種狀態的意思是:
1.原子性(Atomicity)
  原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾,這和前面兩篇博客介紹事務的功能是同樣的概念,所以事務的操做若是成功就必需要徹底應用到數據庫,若是操做失敗則不能對數據庫有任何影響。
2.一致性(Consistency)
  一致性是指事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態.
3.隔離性(Isolation)
  在事務正確提交以前,不容許把事務對該數據的改變提供給任何其餘事務,即在事務正確提交以前,它可能的結果不該該顯示給其餘事務.
4.持久性(Durability)
  持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。算法

2. 事務的做用

當多個線程都開啓事務操做數據庫中的數據時,數據庫系統要能進行隔離操做,以保證各個線程獲取數據的準確性.sql

3. 遇到的併發問題

1.第一類丟失更新:A事務撤銷時,把已經提交的B事務的更新數據覆蓋了.
2.第二類丟失更新:A事務覆蓋B事務已經提交的數據,形成B事務所作操做丟失.
3.髒讀:A事務讀取了事務B中未提交的數據.
4.不可重複讀:A事務屢次讀取的值不一樣,由於該值被B事務修改並提交了.
5.幻讀:A事務兩次讀之間,B事務插入了數據.數據庫

4. 如何解決上面的問題呢?

爲了解決上面的問題,開發者爲MySQL數據庫設計瞭如下四種事務隔離級別:
1.Read Uncommitted(未提交讀):容許髒讀,也就是可能讀取到其餘會話中未提交事務修改的數據.數組

2.Read Committed(提交讀):只能讀取到已經提交的數據。Oracle等多數數據庫默認都是該級別 (不重複讀).session

3.Repeated Read(可重複讀):可重複讀。在同一個事務內的查詢都是事務開始時刻一致的,InnoDB默認級別。在SQL標準中,該隔離級別消除了不可重複讀,可是還存在幻象讀,可是innoDB解決了幻讀.併發

4.Serializable(串行讀):徹底串行化的讀,每次讀都須要得到表級共享鎖,讀寫相互都會阻塞.數據庫設計

隔離級別 髒讀 不可重複度 不幻讀
Read Uncommitted(未提交讀) 可能 可能 可能
Read Committed(提交讀) 不可能 可能 可能
Repeated Read(可重複讀) 不可能 不可能 可能
Serializable(串行讀) 不可能 不可能 不可能

5. 小嚐試

1.查看全局或會話的事務隔離級別函數

SELECT @@global.tx_isolation, @@tx_isolation;

事務隔離級別1
2.修改全局或會話的事務隔離級別源碼分析

SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]

6. MySQL默認Repeated Read隔離級別,按道理並不能解決幻讀問題呀?

如下將先介紹數據庫所涉及的鎖.

7. 鎖的基本敘述

1.鎖簡介
數據庫中的鎖是指一種軟件機制,用來控制防止某個用戶(進程會話)在已經佔用了某種數據資源時,其餘用戶作出影響本用戶數據操做或致使數據非完整性和非一致性問題發生的手段。
2.鎖的級別
按照鎖級別劃分,鎖可分爲共享鎖、排他鎖。

  • 共享鎖(讀鎖)

針對同一塊數據,多個讀操做能夠同時進行而不會互相影響。共享鎖只針對UPDATE時候加鎖,在未對UPDATE操做提交以前,其餘事務只可以獲取最新的記錄但不可以UPDATE操做。

  • 排他鎖(寫鎖)

當前寫操做沒有完成前,阻斷其餘寫鎖和讀鎖。
3.鎖的粒度
按鎖的粒度劃分,鎖可分爲表級鎖、行級鎖、頁級鎖。

  • 行級鎖

開銷大,加鎖慢,會出現死鎖,鎖定力度最小,發生鎖衝突的機率最低,併發度高。

  • 表級鎖

開銷小,加鎖快,不會出現死鎖,鎖定力度大,發生衝突所的機率高,併發度低。

  • 頁面鎖

開銷和加鎖時間介於表鎖和行鎖之間,會出現死鎖,鎖定力度介於表和行行級鎖之間,併發度通常。

8. 悲觀鎖和樂觀鎖

8.1 悲觀鎖

1.基本思想:老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖.因此無論衝突是否真的發生,都會使用鎖機制。
2.悲觀鎖功能:

  • 鎖住讀取的記錄,防止其它事務讀取和更新這些記錄。其它事務會一直阻塞,直到這個事務結束。
  • 悲觀鎖是在使用了數據庫的事務隔離功能的基礎上,獨享佔用的資源,以此保證讀取數據一致性,避免修改丟失。
  • 悲觀鎖可使用Repeatable Read事務,它徹底知足悲觀鎖的要求。

8.2 樂觀鎖

1.基本思想:老是假設最好的狀況,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號機制CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量.
2.解釋:樂觀鎖是一種思想,樂觀鎖不會鎖住任何東西,也就是說,它不依賴數據庫的事務機制,樂觀鎖徹底是應用系統層面的東西。因此它不是一種鎖機制.若是使用樂觀鎖,那麼數據庫就必須加版本字段,不然就只能比較全部字段,但由於浮點類型不能比較,因此實際上沒有版本字段是不可行的

8.3 版本號機制

通常是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛纔讀取到的version值爲當前數據庫中的version值相等時才更新,不然重試更新操做,直到更新成功。

8.4 CAS算法

1.核心思想:Compare and Swap,即比較再交換。
2.過程:假設有A線程準備去修改內存中變量名爲name的值,所以A線程會用之前本身讀到的name變量值和此刻name的值作對比,若是同樣,則代表在變量值沒被修改過,所以能夠更新修改,不然更新失敗.
CAS算法

9. 回到MySQL的重複讀(Repeated Read)事務隔離級別

前面說過,MySQL默認實現了可重複讀的事務隔離級別,可是不能解決幻讀的問題,然而在MySQL數據庫使用可重複讀的事務隔離條件下,並未發生幻讀.MySQL使用MVCC(多版本併發控制)進行了控制.

9.1名詞簡析:

1.MVCC:是multiversion concurrency control的簡稱,也就是多版本併發控制,是個很基本的概念。MVCC的做用是讓事務在並行發生時,在必定隔離級別前提下,能夠保證在某個事務中能實現一致性讀,也就是該事務啓動時根據某個條件讀取到的數據,直到事務結束時,再次執行相同條件,仍是讀到同一份數據,不會發生變化(不會看到被其餘並行事務修改的數據)。
2.read view:InnoDB MVCC使用的內部快照的意思。在不一樣的隔離級別下,事務啓動時(有些狀況下,多是SQL語句開始時)看到的數據快照版本可能也不一樣。在上面介紹的幾個隔離級別下會用到 read view。
3.快照讀: 就是所謂的根據read view去獲取信息和數據,不會加任何的鎖。
4.當前讀:前讀會獲取獲得全部已經提交數據,按照邏輯上來說的話,在一個事務中第一次當前讀和第二次當前讀的中間有新的事務進行DML操做,這個時候倆次當前讀的結果應該是不一致的,可是實際的狀況倒是在當前讀的這個事務還沒提交以前,全部針對當前讀的數據修改和插入都會被阻塞,主要是由於next-key lock解決了當前讀可能會發生幻讀的狀況。
next-key lock當使用主鍵索引進行當前讀的時候,會降級爲record lock(行鎖)

9.2 Read view詳析

InnoDB支持MVCC多版本控制,其中READ COMMITTED和REPEATABLE READ隔離級別是利用consistent read view(一致讀視圖)方式支持的。所謂的consistent read view就是在某一時刻給事務系統trx_sys打snapshot(快照),把當時的trx_sys狀態(包括活躍讀寫事務數組)記下來,以後的全部讀操做根據其事務ID(即trx_id)與snapshot中trx_sys的狀態作比較,以此判斷read view對事務的可見性。
REPEATABLE READ隔離級別(除了GAP鎖以外)和READ COMMITTED隔離級別的差異是建立snapshot時機不一樣。REPEATABLE READ隔離級別是在事務開始時刻,確切的說是第一個讀操做建立read view的時候,READ COMMITTED隔離級別是在語句開始時刻建立read view的。這就意味着REPEATABLE READ隔離級別下面一個事務的SELECT操做只會獲取一個read view,可是READ COMMITTED隔離級別下一個事務是能夠獲取多個read view的。
建立/關閉read view須要持有trx_sys->mutex,會下降系統性能,5.7版本對此進行優化,在事務提交時session會cache只讀事務的read view。

9.3 read view 判斷當前版本數據項是否可見

在InnoDB中,建立一個新事務的時候,InnoDB會將當前系統中的活躍事務列表(trx_sys->trx_list)建立一個副本(read view),副本中保存的是系統當前不該該被本事務看到的其餘事務id列表。當用戶在這個事務中要讀取該行記錄的時候,InnoDB會將該行當前的版本號與該read view進行比較。
具體的算法以下:
設該行的當前事務id爲trx_id,read view中最先的事務id爲trx_id_min, 最遲的事務id爲trx_id_max。
若是trx_id< trx_id_min的話,那麼代表該行記錄所在的事務已經在本次新事務建立以前就提交了,因此該行記錄的當前值是可見的。
若是trx_id>trx_id_max的話,那麼代表該行記錄所在的事務在本次新事務建立以後纔開啓,因此該行記錄的當前值不可見。
若是trx_id_min <= trx_id <= trx_id_max, 那麼代表該行記錄所在事務在本次新事務建立的時候處於活動狀態,從trx_id_min到trx_id_max進行遍歷,若是trx_id等於他們之中的某個事務id的話,那麼不可見,如圖:

可見流程圖

從該行記錄的DB_ROLL_PTR指針所指向的回滾段中取出最新的undo-log的版本號的數據,將該可見行的值返回。
須要注意的是,新建事務(當前事務)與正在內存中commit 的事務不在活躍事務鏈表中。
在具體多版本控制中咱們先來看下源碼:

函數:read_view_sees_trx_id。
read_view中保存了當前全局的事務的範圍:
【low_limit_id, up_limit_id】

1.當行記錄的事務ID小於當前系統的最小活動id,就是可見的。
      if (trx_id < view->up_limit_id) {
            return(TRUE);
          }
2.當行記錄的事務ID大於當前系統的最大活動id(也就是還沒有分配的下一個事務的id),就是不可見的。
      if (trx_id >= view->low_limit_id) {
            return(FALSE);
          }
3.當行記錄的事務ID在活動範圍之中時,判斷是否在活動鏈表中,若是在就不可見,若是不在就是可見的。
      for (i = 0; i < n_ids; i++) {
            trx_id_t view_trx_id
              = read_view_get_nth_trx_id(view, n_ids - i - 1);
            if (trx_id <= view_trx_id) {
            return(trx_id != view_trx_id);
            }
          }

Read view 圖解
圖解

結語

筆者水平有限,文中若有不妥,請你們多多指教,MySQL數據庫事務機制還有不少須要深刻研究的,咱們仍需不斷鑽研。

參考引用MySQL源碼分析

相關文章
相關標籤/搜索