天天看點

關于mysql資料庫MVCC自己的一點了解

作者:Java掃地僧

什麼是mvcc

mvcc,也就是多版本并發控制,是為了在讀取資料時不加鎖來提高讀取效率和并發性的一種手段。

資料庫并發有以下幾種場景:

  • 讀-讀:不存在任何問題。
  • 讀-寫:有線程安全問題,可能出現髒讀、幻讀、不可重複讀。
  • 寫-寫:有線程安全問題,可能存在更新丢失等。

了解兩個概念

目前讀

像select lock in share mode(共享鎖), select for update ; update, insert ,delete(排他鎖)這些操作都是一種目前讀,為什麼叫目前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其他并發事務不能修改目前記錄,會對讀取的記錄進行加鎖。

快照讀(提高資料庫的并發查詢能力)

像不加鎖的select操作就是快照讀,即不加鎖的非阻塞讀;快照讀的前提是隔離級别不是串行級别,串行級别下的快照讀會退化成目前讀;之是以出現快照讀的情況,是基于提高并發性能的考慮,快照讀的實作是基于多版本并發控制,即MVCC,可以認為MVCC是行鎖的一個變種,但它在很多情況下,避免了加鎖操作,降低了開銷;既然是基于多版本,即快照讀可能讀到的并不一定是資料的最新版本,而有可能是之前的曆史版本

MVCC的實作

MVCC多版本并發控制指的是維持一個資料的多個版本,使得讀寫操作沒有沖突,快照讀是MySQL為實作MVCC的一個非阻塞讀功能。MVCC子產品在MySQL中的具體實作是由三個隐式字段,undo日志、read view三個元件來實作的

回顧事務的特性

  • 原子性:通過undolog實作。
  • 持久性:通過redolog實作。
  • 隔離性:通過加鎖(目前讀)&MVCC(快照讀)實作。
  • 一緻性:通過undolog、redolog、隔離性共同實作。

回顧事務的隔離級别

  • 讀未送出:允許讀取尚未送出的資料變更。可能會導緻髒讀、幻讀或不可重複讀。
  • 讀已送出:允許讀取已經送出的資料。可能會導緻幻讀和不可重複讀。
  • 可重複讀:對同一字段的多次讀取結果都是一緻的,除非資料是被本身事務自己所修改。可能會導緻幻讀。
  • 可串行化:最高隔離級别。

在讀已送出和可重複讀隔離級别下的快照讀,都是基于MVCC實作的!

mvcc實作原理

  1. 三個隐式字段
關于mysql資料庫MVCC自己的一點了解
  • trx_id:事務id,每進行一次事務操作,就會自增1。(最近修改事務id,記錄建立這條記錄或者最後一次修改該記錄的事務id)
  • roll_pointer:復原指針,用于找到上一個版本的資料,結合undolog進行復原。
  • row_id:隐藏的主鍵,如果資料表沒有主鍵,且沒有唯一非空字段,那麼innodb會自動生成一個6位元組的row_id

2.undo log

undolog被稱之為復原日志,表示在進行insert,delete,update操作的時候産生的友善復原的日志

​ 當進行insert操作的時候,産生的undolog隻在事務復原的時候需要,并且在事務送出之後可以被立刻丢棄

​ 當進行update和delete操作的時候,産生的undolog不僅僅在事務復原的時候需要,在快照讀的時候也需要,是以不能随便删除,隻有在快照讀或事務復原不涉及該日志時,對應的日志才會被purge線程統一清除(當資料發生更新和删除操作的時候都隻是設定一下老記錄的deleted_bit,并不是真正的将過時的記錄删除,因為為了節省磁盤空間,innodb有專門的purge線程來清除deleted_bit為true的記錄,如果某個記錄的deleted_id為true,并且DB_TRX_ID相對于purge線程的read view 可見,那麼這條記錄一定是可以被清除的)

  1. Read View

Read View是事務進行快照讀操作的時候生産的讀視圖,在該事務執行快照讀的那一刻,會生成一個資料系統目前的快照,記錄并維護系統目前活躍事務的id,事務的id值是遞增的。

​ 其實Read View的最大作用是用來做可見性判斷的,也就是說當某個事務在執行快照讀的時候,對該記錄建立一個Read View的視圖,把它當作條件去判斷目前事務能夠看到哪個版本的資料,有可能讀取到的是最新的資料,也有可能讀取的是目前行記錄的undolog中某個版本的資料

在一個readview快照中主要包括以下這些字段:

關于mysql資料庫MVCC自己的一點了解

m_ids:活躍的事務就是指還沒有commit的事務。

min_trx_id:記錄trx_list清單中事務ID最小的ID(1)

max_trx_id:例如m_ids中的事務id為(1,2,3),那麼下一個應該配置設定的事務id就是4,max_trx_id就是4。

creator_trx_id:執行select讀這個操作的事務的id。

readview如何判斷版本鍊中的哪個版本可用呢?(核心重點!)

關于mysql資料庫MVCC自己的一點了解

從上到下分别為(1)(2)(3)(4),依次進行解釋

trx_id表示資料行中隐式字段 DB_TRX_ID;

(1)擷取目前最新記錄事務id等于進行讀操作的事務id,說明是我讀取我自己建立的記錄,那麼為什麼不可以呢。

(2)擷取目前最新記錄事務id小于最小的活躍事務id,說明要讀取的事務已經送出,那麼可以讀取。

(3)max_trx_id表示生成readview時,配置設定給下一個事務的id,如果要讀取記錄的事務id大于max_trx_id,說明該id已經不在該readview版本鍊中了,代表DB_TRX_ID所在的記錄在Read View生成後才出現的,那麼對于目前事務肯定不可見。

(4)判斷DB_TRX_ID是否在活躍事務中,如果在,則代表在Read View生成時刻,這個事務還是活躍狀态,還沒有commit,修改的資料,目前事務也是看不到,如果不在,則說明這個事務在Read View生成之前就已經開始commit,那麼修改的結果是能夠看見的。

關于mysql資料庫MVCC自己的一點了解

Read View遵循的可見性算法主要是将要被修改的資料的最新記錄中的DB_TRX_ID(目前事務id)取出來,與系統目前其他活躍事務的id去對比,如果DB_TRX_ID跟Read View的屬性做了比較,不符合可見性(即目前資料庫中最新的這條資料目前讀事務不可見),那麼就通過DB_ROLL_PTR復原指針去取出undolog中的DB_TRX_ID做比較,即周遊連結清單中的DB_TRX_ID,直到找到滿足條件的DB_TRX_ID,這個DB_TRX_ID所在的舊記錄就是目前事務能看到的最新老版本資料。

RC、RR級别下的InnoDB快照讀有什麼不同

​ 因為Read View生成時機的不同,進而造成RC、RR級别下快照讀的結果的不同

​ 1、在RR級别下的某個事務的對某條記錄的第一次快照讀會建立一個快照即Read View,将目前系統活躍的其他事務記錄起來,此後在調用快照讀的時候,還是使用的是同一個Read View,是以隻要目前事務在其他事務送出更新之前使用過快照讀,那麼之後的快照讀使用的都是同一個Read View,是以對之後的修改不可見

​ 2、在RR級别下,快照讀生成Read View時,Read View會記錄此時所有其他活動和事務的快照,這些事務的修改對于目前事務都是不可見的,而早于Read View建立的事務所做的修改均是可見

​ 3、在RC級别下,事務中,每次快照讀都會新生成一個快照和Read View,這就是我們在RC級别下的事務中可以看到别的事務送出的更新的原因。

​ 總結:在RC隔離級别下,是每個快照讀都會生成并擷取最新的Read View,而在RR隔離級别下,則是同一個事務中的第一個快照讀才會建立Read View,之後的快照讀擷取的都是同一個Read View.

繼續閱讀