日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Mysql事务隔离级别及MVCC(多版本并发控制)

發布時間:2025/3/15 数据库 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Mysql事务隔离级别及MVCC(多版本并发控制) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、MySQL事務隔離級別

先注明一點:以下討論都是在多事務并發的情境下討論的
事務的特性(InnoDB引擎才有事務):
ACID
原子性:一個事務不可再分割,要么都執行要么都不執行
一致性:一個事務執行會使數據從一個一致狀態切換到另外一個一致狀態
隔離性:一個事務的執行不受其他事務的干擾
持久性:一個事務一旦提交,則會永久的改變數據庫的數據.

  • 事務隔離級別
    ? 事務隔離級別指的是在處理同一個數據的多個事務中,一個事務修改數據后,其他事務何時能看到修改后的結果。數據庫事務的隔離級別有4個,由低到高依次為Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決臟讀 、不可重復讀 、幻讀這幾類問題。
  • 臟讀,幻讀,不可重復讀
  • 1.臟讀:臟讀就是指當一個事務正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。2.不可重復讀:是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由于第二個事務的修改,那么第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。(即不能讀到相同的數據內容),例如,一個編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重復。如果只有在作者全部完成編寫后編輯人員才可以讀取文檔,則可以避免該問題。3.幻讀:是指當事務不是獨立執行時發生的一種現象,例如:第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。注意:不可重復讀的重點是修改 :同樣的條件,你讀取過的數據,再次讀取出來發現值不一樣了幻讀的重點在于新增或者刪除同樣的條件,第 1 次和第 2 次讀出來的記錄數不一樣。
  • MySQL數據庫事務隔離級別主要有四種:
  • 1.Serializable:串行化,一個事務一個事務的執行。是最高的事務隔離級別,同時代價也花費 最高,性能很低,一般很少使用,在該級別下,事務順序執行,不僅可以避免臟讀、不可重復讀, 還避免了幻像讀。 2.Repeatable read:重復讀,無論其他事務是否修改并提交了數據,在這個事務中看到的數據值 始終不受其他事務影響,可以避免不可重復讀,但還有可能出現幻讀 ; 3.Read committed:讀取已提交,其他事務提交了對數據的修改后,本事務就能讀取到修改后的 數據值,避免了臟讀,但是可能會造成不可重復讀; 4.Read uncommitted:讀取未提交,其他事務只要修改了數據,即使未提交,本事務也能看到 修改后的數據值,就可能出現臟讀;
  • MySQL數據庫默認使用可重復讀( Repeatable read)
    各級別解決的問題如下:
  • 具體級別對應的問題及詳細過程
    1.Read Uncommitted(能讀到未提交的數據)

  • 2.Read committed(解決了臟讀的問題(如下圖,事務二最終讀取到的是a=2的正確數據),但當前會話只能讀取到其他事務提交的數據,未提交的數據讀不到,出現不可重復讀的問題))


    3.Repeatable Read(可重讀)
    這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在并發讀取數據時,會看到同樣的數據行;但可能出現幻讀現象:當用戶讀取某一范圍的數據行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數據行時,會發現有新的“幻影” 行。InnoDB和Falcon存儲引擎通過多版本并發控制(MVCC)機制解決了該問題。
    其實對于幻讀, MySQL的InnoDB引擎默認的RR級別已經通過MVCC自動幫我們解決了, 所以該級別下, 你也模擬不出幻讀的場景; 退回到 RC 隔離級別的話, 又容易把幻讀和不可重復讀搞混淆, 具體可以參考《高性能MySQL》對 RR 隔離級別的描述, 理論上RR級別是無法解決幻讀的問題, 但是由于InnoDB引擎的RR級別還使用了MVCC, 所以也就避免了幻讀的出現!但是MVCC雖然解決了幻讀問題, 但嚴格來說只是解決了部分幻讀問題
    解決不可重復讀
    出現幻讀


    4.serializable
    在該隔離級別下,只允許一個事務在執行,其它事務必須等待這個事務執行完后才能執行,也就解決了上面的問題,但是效率太低了,沒有并發,只是單純的串行。
    例:事務A操作時將整張表鎖住了,當事務B嘗試操作時,會被阻塞,直到事務A提交commit以后,事務B的操作才會返回結果,而且事務B等待的時間可以設置,超出時間就error。
    不可重復讀和幻讀比較:
    兩者有些相似,前者針對的是update或delete,后者針對的insert。

    二、MVCC

    1.MVCC:
    全稱是Mutil-Version Concurrency Control,翻譯成中文是多版本并發控制,MySQL就利用了MVCC來判斷在一個事務中,哪個數據可以被讀出來,哪個數據不能被讀出來。

    2.多版本:
    在看MVCC之前,我們有必要知道另外一個知識點,數據庫存儲一行行數據,是分為兩個部分來存儲的,一個是數據行的額外信息(本篇博客不涉及),一個是真實的數據記錄,MySQL會為每一行真實數據記錄添加兩三個隱藏的字段:

  • row_id:
    非必須,如果表中有自定義的主鍵或者有Unique鍵,就不會添加row_id字段,如果兩者都沒有,MySQL會“自作主張”添加row_id字段。
  • transaction_id:
    必須,事務Id,代表這一行數據是由哪個事務id創建的。
  • roll_pointer:
    必須,回滾指針,指向這行數據的上一個版本。

    在這里需要著重說明下事務id,當我們開啟一個事務,并不會馬上獲得事務id,哪怕我們在事務中執行select語句,也是沒有事務id的(事務id為0),只有執行insert/update/delete語句才能獲得事務id,這一點尤為重要。其中和MVCC緊密相關的是transaction_id和roll_pointer兩個字段,在開發過程中,我們無需關心,但是要研究MVCC,我們必須關心。
  • 如果有類似這樣的一行數據:

    代表這行數據是由transaction_id為9的事務創建出來的,roll_pointer是空的,因為這是一條新紀錄。實際上,roll_pointer并不是空的,如果真要解釋,需要繞一大圈,理解成空的,問題也不大。

    當我們開啟事務,對這條數據進行修改,會變成這樣:
    有點感覺了吧,這就像一個單向鏈表,稱之為“版本鏈”,最上面的數據是這個數據的最新版本,roll_pointer指向這個數據的舊版本,給人的感覺就是一行數據有多個版本,是不是符合“多版本并發控制”中的“多版本”這個概念,
    那么“并發控制”又是怎么做到的呢,別急,繼續往下看。

    3.ReadView:
    下面又要引出一個新的概念:ReadView。
    對于READ UNCOMMITTED來說,可以讀取到其他事務還沒有提交的數據,所以直接把這個數據的最新版本讀出來就可以了,對SERIALIZABLE來說,是用加鎖的方式來訪問記錄。剩下的就是READ COMMITTED和REPEATABLE READ,這兩個事務隔離級別都要保證讀到的數據是其他事務已經提交的,也就是不能無腦把一行數據的最新版本給讀出來了,但是這兩個還是有一定的區別,最核心的問題就在于“我到底可以讀取這個數據的哪個版本”。為了解決這個問題,ReadView的概念就出現了,ReadView包含四個比較重要的內容:

  • m_ids:表示在生成ReadView時,系統中活躍的事務id集合。
  • min_trx_id:表示在生成ReadView時,系統中活躍的最小事務id,也就是 m_ids中的最小值。
  • max_trx_id:表示在生成ReadView時,系統應該分配給下一個事務的id。
  • creator_trx_id:表示生成該ReadView的事務id。
  • 有了這個ReadView,只要按照下面的判斷方式就可以解決“我到底可以讀取這個數據的哪個版本”這個千古難題了:

  • 如果被訪問的版本的trx_id和ReadView中的creator_trx_id相同,就意味著當前版本就是由你“造成”的,可以讀出來。
  • 如果被訪問的版本的trx_id小于ReadView中的min_trx_id,表示生成該版本的事務在創建ReadView的時候,已經提交了,所以該版本可以讀出來。
  • 如果被訪問版本的trx_id大于或等于ReadView中的max_trx_id值,說明生成該版本的事務在當前事務生成ReadView后才開啟,所以該版本不可以被讀出來。
  • 如果生成被訪問版本的trx_id在min_trx_id和max_trx_id之間,那就需要判斷下trx_id在不在m_ids中:如果在,說明創建ReadView的時候,生成該版本的事務還是活躍的(沒有被提交),該版本不可以被讀出來;如果不在,說明創建ReadView的時候,生成該版本的事務已經被提交了,該版本可以被讀出來。
  • 如果某個數據的最新版本不可以被讀出來,就順著roll_pointer找到該數據的上一個版本,繼續做如上的判斷,以此類推,如果第一個版本也不可見的話,代表該數據對當前事務完全不可見,查詢結果就不包含這條記錄了。
  • 看完上面的描述,是不是覺得“云里霧里”,“不知所云”,甚至“腦闊疼,整個人都不好了”。

    我們換個方法來解釋,看會不會更容易理解點:
    在事務啟動的一瞬間(執行CURD操作),會創建出ReadView,對于一個數據版本的trx_id來說,有以下三種情況:

  • 如果落在低水位,表示生成這個版本的事務已經提交了,或者是當前事務自己生成的,這個版本可見。
  • 如果落在高水位,表示生成這個版本的事務是未來才創建的,這個版本不可見。 如果落在中間水位,包含兩種情況:
    a.如果當前版本的trx_id在活躍事務列表中,代表這個版本是由還沒有提交的事務生成的,這個版本不可見;
    b.如果當前版本的trx_id不在活躍事務列表中,代表這個版本是由已經提交的事務生成的,這個版本可見。
  • 上面我比較簡單的解釋了下ReadView,用了兩種方式來說明如何判斷當前數據版本是否可見,不知道各位看官是不是有了一個比較模糊的概念,有了ReadView的基本概念,我們就可以具體看下READ COMMITTED、REPEATABLE READ這兩個事務隔離級別為什么讀到的數據是不同的,以及上述規則是如何應用的。

    READ COMMITTED——每次讀取數據都會創建ReadView

    假設,現在系統只有一個活躍的事務T,事務id是100,事務中修改了數據,但是還沒有提交,形成的版本鏈是這樣的:


    現在A事務啟動,并且執行了select語句,此時會創建出一個ReadView,m_ids是【100】,min_trx_id是100, max_trx_id是101,creator_trx_id是0。為什么m_ids只有一個,為什么creator_trx_id是0?這里再次強調下,只有在事務中執行insert/update/delete語句才能獲得事務id。

    那么A事務執行的select語句會讀到什么數據呢?

  • 判斷最新的數據版本,name是“夢境地底王”,對應的trx_id是100,trx_id在m_ids里面,說明當前事務是活躍事務,這個數據版本是由還沒有提交的事務創建的,所以這個版本不可見。
  • 順著roll_pointer找到這個數據的上一個版本,name是“地底王”,對應的trx_id是99,而ReadView中的min_trx_id是100,trx_id<min_trx_id,代表當前數據版本是由已經提交的事務創建的,該版本可見。
    所以讀到的數據的name是“地底王”。
  • 我們把事務T提交了,事務A再次執行select語句,此時,事務A再次創建出ReadView,m_ids是【】,min_trx_id是0, max_trx_id是101,creator_trx_id是0。
    因為事務T已經提交了,所以沒有活躍的事務。

    那么事務A第二次執行select語句又會讀到什么數據呢?

  • 判斷最新的數據版本,name是“夢境地底王”,對應的trx_id是100,不在m_ids里面,說明這個數據版本是由已經提交的事務創建的,該版本可見。所以讀到的數據的name是“夢境地底王”。
  • REPEATABLE READ ——首次讀取數據會創建ReadView

    假設,現在系統只有一個活躍的事務T,事務id是100,事務中修改了數據,但是還沒有提交,形成的版本鏈是這樣的:

    現在A事務啟動,并且執行了select語句,此時會創建出一個ReadView,m_ids是【100】,min_trx_id是100, max_trx_id是101,creator_trx_id是0。

    那么A事務執行的select語句會讀到什么數據呢?

  • 判斷最新的數據版本,name是“夢境地底王”,對應的trx_id是100,trx_id在m_ids里面,說明當前事務是活躍事務,這個數據版本是由還沒有提交的事務創建的,所以這個版本不可見。
  • 順著roll_ponit找到這個數據的上一個版本,name是“地底王”,對應的trx_id是99,而ReadView中的min_trx_id是100,trx_id<min_trx_id,代表當前數據版本是由已經提交的事務創建的,該版本可見。
    所以讀到的數據的name是“地底王”。
  • 細心的你,一定發現了,這里我就是復制粘貼,因為在REPEATABLE READ事務隔離級別下,事務A首次執行select語句創建出來的ReadView和在READ COMMITTED事務隔離級別下,事務A首次執行select語句創建出來的ReadView是一樣的,所以判斷流程也是一樣的,所以我就偷懶了,copy走起。

    隨后,事務T提交了事務,由于REPEATABLE READ是首次讀取數據才會創建ReadView,所以事務A再次執行select語句,不會再創建ReadView,用的還是上一次的ReadView,所以判斷流程和上面也是一樣的,所以讀到的name還是“地底王”。

    參考:https://segmentfault.com/a/1190000014837747
    參考:https://www.cnblogs.com/CodeBear/p/12710670.html

    與50位技術專家面對面20年技術見證,附贈技術全景圖

    總結

    以上是生活随笔為你收集整理的Mysql事务隔离级别及MVCC(多版本并发控制)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。