溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

關(guān)于MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC的詳細知識

發(fā)布時間:2020-05-28 14:33:25 來源:PHP中文網(wǎng) 閱讀:240 作者:三月 欄目:MySQL數(shù)據(jù)庫

下文給大家?guī)碛嘘P(guān)MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC內(nèi)容,相信大家一定看過類似的文章。我們給大家?guī)淼挠泻尾煌兀恳黄饋砜纯凑牟糠职?,相信看完MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC你一定會有所收獲。

1. 什么是事務(wù)?

事務(wù)必須服從ISO/IEC所制定的ACID原則。ACID是原子性(atomicity)、一致性(consistency)、隔離性(isolation)、持久性(durability)的縮寫,這四種狀態(tài)的意思是:
1.原子性(Atomicity)
  原子性是指事務(wù)包含的所有操作要么全部成功,要么全部失敗回滾,這和前面兩篇博客介紹事務(wù)的功能是一樣的概念,因此事務(wù)的操作如果成功就必須要完全應(yīng)用到數(shù)據(jù)庫,如果操作失敗則不能對數(shù)據(jù)庫有任何影響。
2.一致性(Consistency)
  一致性是指事務(wù)必須使數(shù)據(jù)庫從一個一致性狀態(tài)變換到另一個一致性狀態(tài).
3.隔離性(Isolation)
  在事務(wù)正確提交之前,不允許把事務(wù)對該數(shù)據(jù)的改變提供給任何其他事務(wù),即在事務(wù)正確提交之前,它可能的結(jié)果不應(yīng)該顯示給其他事務(wù).
4.持久性(Durability)
  持久性是指一個事務(wù)一旦被提交了,那么對數(shù)據(jù)庫中的數(shù)據(jù)的改變就是永久性的,即便是在數(shù)據(jù)庫系統(tǒng)遇到故障的情況下也不會丟失提交事務(wù)的操作。

2. 事務(wù)的作用

當(dāng)多個線程都開啟事務(wù)操作數(shù)據(jù)庫中的數(shù)據(jù)時,數(shù)據(jù)庫系統(tǒng)要能進行隔離操作,以保證各個線程獲取數(shù)據(jù)的準(zhǔn)確性.

3. 遇到的并發(fā)問題

1.第一類丟失更新:A事務(wù)撤銷時,把已經(jīng)提交的B事務(wù)的更新數(shù)據(jù)覆蓋了.
2.第二類丟失更新:A事務(wù)覆蓋B事務(wù)已經(jīng)提交的數(shù)據(jù),造成B事務(wù)所做操作丟失.
3.臟讀:A事務(wù)讀取了事務(wù)B中未提交的數(shù)據(jù).
4.不可重復(fù)讀:A事務(wù)多次讀取的值不同,因為該值被B事務(wù)修改并提交了.
5.幻讀:A事務(wù)兩次讀之間,B事務(wù)插入了數(shù)據(jù).

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

為了解決上面的問題,開發(fā)者為MySQL數(shù)據(jù)庫設(shè)計了以下四種事務(wù)隔離級別:
1.Read Uncommitted(未提交讀):允許臟讀,也就是可能讀取到其他會話中未提交事務(wù)修改的數(shù)據(jù).

2.Read Committed(提交讀):只能讀取到已經(jīng)提交的數(shù)據(jù)。Oracle等多數(shù)數(shù)據(jù)庫默認(rèn)都是該級別 (不重復(fù)讀).

3.Repeated Read(可重復(fù)讀):可重復(fù)讀。在同一個事務(wù)內(nèi)的查詢都是事務(wù)開始時刻一致的,InnoDB默認(rèn)級別。在SQL標(biāo)準(zhǔn)中,該隔離級別消除了不可重復(fù)讀,但是還存在幻象讀,但是innoDB解決了幻讀.

4.Serializable(串行讀):完全串行化的讀,每次讀都需要獲得表級共享鎖,讀寫相互都會阻塞.

隔離級別臟讀不可重復(fù)度不幻讀
Read Uncommitted(未提交讀)可能可能可能
Read Committed(提交讀)不可能可能可能
Repeated Read(可重復(fù)讀)不可能不可能可能
Serializable(串行讀)不可能不可能不可能

5. 小嘗試

1.查看全局或會話的事務(wù)隔離級別

SELECT @@global.tx_isolation, @@tx_isolation;

關(guān)于MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC的詳細知識

2.修改全局或會話的事務(wù)隔離級別

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

6. MySQL默認(rèn)Repeated Read隔離級別,按道理并不能解決幻讀問題呀?

以下將先介紹數(shù)據(jù)庫所涉及的鎖.

7. 鎖的基本敘述

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

  • 共享鎖(讀鎖)

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

  • 排他鎖(寫鎖)

當(dāng)前寫操作沒有完成前,阻斷其他寫鎖和讀鎖。
3.鎖的粒度
按鎖的粒度劃分,鎖可分為表級鎖、行級鎖、頁級鎖。

  • 行級鎖

開銷大,加鎖慢,會出現(xiàn)死鎖,鎖定力度最小,發(fā)生鎖沖突的概率最低,并發(fā)度高。

  • 表級鎖

開銷小,加鎖快,不會出現(xiàn)死鎖,鎖定力度大,發(fā)生沖突所的概率高,并發(fā)度低。

  • 頁面鎖

開銷和加鎖時間介于表鎖和行鎖之間,會出現(xiàn)死鎖,鎖定力度介于表和行行級鎖之間,并發(fā)度一般。

8. 悲觀鎖和樂觀鎖

8.1 悲觀鎖

1.基本思想:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖.所以不管沖突是否真的發(fā)生,都會使用鎖機制。
2.悲觀鎖功能:

  • 鎖住讀取的記錄,防止其它事務(wù)讀取和更新這些記錄。其它事務(wù)會一直阻塞,直到這個事務(wù)結(jié)束。
  • 悲觀鎖是在使用了數(shù)據(jù)庫的事務(wù)隔離功能的基礎(chǔ)上,獨享占用的資源,以此保證讀取數(shù)據(jù)一致性,避免修改丟失。
  • 悲觀鎖可以使用Repeatable Read事務(wù),它完全滿足悲觀鎖的要求。
8.2 樂觀鎖

1.基本思想:總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號機制和CAS算法實現(xiàn)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量.
2.解釋:樂觀鎖是一種思想,樂觀鎖不會鎖住任何東西,也就是說,它不依賴數(shù)據(jù)庫的事務(wù)機制,樂觀鎖完全是應(yīng)用系統(tǒng)層面的東西。所以它不是一種鎖機制.如果使用樂觀鎖,那么數(shù)據(jù)庫就必須加版本字段,否則就只能比較所有字段,但因為浮點類型不能比較,所以實際上沒有版本字段是不可行的

8.3 版本號機制

一般是在數(shù)據(jù)表中加上一個數(shù)據(jù)版本號version字段,表示數(shù)據(jù)被修改的次數(shù),當(dāng)數(shù)據(jù)被修改時,version值會加一。當(dāng)線程A要更新數(shù)據(jù)值時,在讀取數(shù)據(jù)的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當(dāng)前數(shù)據(jù)庫中的version值相等時才更新,否則重試更新操作,直到更新成功。

8.4 CAS算法

1.核心思想:Compare and Swap,即比較再交換。
2.過程:假設(shè)有A線程準(zhǔn)備去修改內(nèi)存中變量名為name的值,因此A線程會用以前自己讀到的name變量值和此刻name的值做對比,如果一樣,則表明在變量值沒被修改過,因此可以更新修改,否則更新失敗.
關(guān)于MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC的詳細知識

9. 回到MySQL的重復(fù)讀(Repeated Read)事務(wù)隔離級別

前面說過,MySQL默認(rèn)實現(xiàn)了可重復(fù)讀的事務(wù)隔離級別,但是不能解決幻讀的問題,然而在MySQL數(shù)據(jù)庫使用可重復(fù)讀的事務(wù)隔離條件下,并未發(fā)生幻讀.MySQL使用MVCC(多版本并發(fā)控制)進行了控制.

9.1名詞簡析:

1.MVCC:是multiversion concurrency control的簡稱,也就是多版本并發(fā)控制,是個很基本的概念。MVCC的作用是讓事務(wù)在并行發(fā)生時,在一定隔離級別前提下,可以保證在某個事務(wù)中能實現(xiàn)一致性讀,也就是該事務(wù)啟動時根據(jù)某個條件讀取到的數(shù)據(jù),直到事務(wù)結(jié)束時,再次執(zhí)行相同條件,還是讀到同一份數(shù)據(jù),不會發(fā)生變化(不會看到被其他并行事務(wù)修改的數(shù)據(jù))。
2.read view:InnoDB MVCC使用的內(nèi)部快照的意思。在不同的隔離級別下,事務(wù)啟動時(有些情況下,可能是SQL語句開始時)看到的數(shù)據(jù)快照版本可能也不同。在上面介紹的幾個隔離級別下會用到 read view。
3.快照讀: 就是所謂的根據(jù)read view去獲取信息和數(shù)據(jù),不會加任何的鎖。
4.當(dāng)前讀:前讀會獲取得到所有已經(jīng)提交數(shù)據(jù),按照邏輯上來講的話,在一個事務(wù)中第一次當(dāng)前讀和第二次當(dāng)前讀的中間有新的事務(wù)進行DML操作,這個時候倆次當(dāng)前讀的結(jié)果應(yīng)該是不一致的,但是實際的情況卻是在當(dāng)前讀的這個事務(wù)還沒提交之前,所有針對當(dāng)前讀的數(shù)據(jù)修改和插入都會被阻塞,主要是因為next-key lock解決了當(dāng)前讀可能會發(fā)生幻讀的情況。
next-key lock當(dāng)使用主鍵索引進行當(dāng)前讀的時候,會降級為record lock(行鎖)

9.2 Read view詳析

InnoDB支持MVCC多版本控制,其中READ COMMITTED和REPEATABLE READ隔離級別是利用consistent read view(一致讀視圖)方式支持的。所謂的consistent read view就是在某一時刻給事務(wù)系統(tǒng)trx_sys打snapshot(快照),把當(dāng)時的trx_sys狀態(tài)(包括活躍讀寫事務(wù)數(shù)組)記下來,之后的所有讀操作根據(jù)其事務(wù)ID(即trx_id)與snapshot中trx_sys的狀態(tài)做比較,以此判斷read view對事務(wù)的可見性。
REPEATABLE READ隔離級別(除了GAP鎖之外)和READ COMMITTED隔離級別的差別是創(chuàng)建snapshot時機不同。REPEATABLE READ隔離級別是在事務(wù)開始時刻,確切的說是第一個讀操作創(chuàng)建read view的時候,READ COMMITTED隔離級別是在語句開始時刻創(chuàng)建read view的。這就意味著REPEATABLE READ隔離級別下面一個事務(wù)的SELECT操作只會獲取一個read view,但是READ COMMITTED隔離級別下一個事務(wù)是可以獲取多個read view的。
創(chuàng)建/關(guān)閉read view需要持有trx_sys->mutex,會降低系統(tǒng)性能,5.7版本對此進行優(yōu)化,在事務(wù)提交時session會cache只讀事務(wù)的read view。

9.3 read view 判斷當(dāng)前版本數(shù)據(jù)項是否可見

在InnoDB中,創(chuàng)建一個新事務(wù)的時候,InnoDB會將當(dāng)前系統(tǒng)中的活躍事務(wù)列表(trx_sys->trx_list)創(chuàng)建一個副本(read view),副本中保存的是系統(tǒng)當(dāng)前不應(yīng)該被本事務(wù)看到的其他事務(wù)id列表。當(dāng)用戶在這個事務(wù)中要讀取該行記錄的時候,InnoDB會將該行當(dāng)前的版本號與該read view進行比較。
具體的算法如下:
設(shè)該行的當(dāng)前事務(wù)id為trx_id,read view中最早的事務(wù)id為trx_id_min, 最遲的事務(wù)id為trx_id_max。
如果trx_id< trx_id_min的話,那么表明該行記錄所在的事務(wù)已經(jīng)在本次新事務(wù)創(chuàng)建之前就提交了,所以該行記錄的當(dāng)前值是可見的。
如果trx_id>trx_id_max的話,那么表明該行記錄所在的事務(wù)在本次新事務(wù)創(chuàng)建之后才開啟,所以該行記錄的當(dāng)前值不可見。
如果trx_id_min <= trx_id <= trx_id_max, 那么表明該行記錄所在事務(wù)在本次新事務(wù)創(chuàng)建的時候處于活動狀態(tài),從trx_id_min到trx_id_max進行遍歷,如果trx_id等于他們之中的某個事務(wù)id的話,那么不可見,如圖:

關(guān)于MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC的詳細知識

從該行記錄的DB_ROLL_PTR指針?biāo)赶虻幕貪L段中取出最新的undo-log的版本號的數(shù)據(jù),將該可見行的值返回。
需要注意的是,新建事務(wù)(當(dāng)前事務(wù))與正在內(nèi)存中commit 的事務(wù)不在活躍事務(wù)鏈表中。
在具體多版本控制中我們先來看下源碼:

函數(shù):read_view_sees_trx_id。
read_view中保存了當(dāng)前全局的事務(wù)的范圍:
【low_limit_id, up_limit_id】

1.當(dāng)行記錄的事務(wù)ID小于當(dāng)前系統(tǒng)的最小活動id,就是可見的。
      if (trx_id < view->up_limit_id) {
            return(TRUE);
          }
2.當(dāng)行記錄的事務(wù)ID大于當(dāng)前系統(tǒng)的最大活動id(也就是尚未分配的下一個事務(wù)的id),就是不可見的。
      if (trx_id >= view->low_limit_id) {
            return(FALSE);
          }
3.當(dāng)行記錄的事務(wù)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 圖解:

關(guān)于MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC的詳細知識

對于上文關(guān)于MySQL數(shù)據(jù)庫的事務(wù)隔離和MVCC,大家覺得是自己想要的嗎?如果想要了解更多相關(guān),可以繼續(xù)關(guān)注我們的行業(yè)資訊板塊。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI