相關學習推薦:mysql教學
全名為Multi-Version Concurrency Control,即多版本並發控制
,主要是為了提高資料庫的並發效能
。以下文章都是圍繞著InnoDB引擎來講,因為myIsam不支援事務。
同一行資料平時發生讀寫請求時,會上鎖阻塞
住。但mvcc用更好的方式去處理讀取—寫請求,做到在發生讀取—寫請求衝突時不用加鎖
。
這個讀是指的快照讀
,而不是目前讀
,目前讀是一種加鎖操作,是悲觀鎖定
。
那它到底是怎麼做到讀—寫不用加鎖
的,快照讀
和目前讀
又是什麼鬼,跟著你們的貼心老哥
,繼續往下看。
什麼是MySQL InnoDB下的目前讀取和快照讀取?
它讀取的資料庫記錄,都是目前最新
的版本
,會對目前讀取的資料進行加鎖
,防止其他交易修改資料。是悲觀鎖定
的一種操作。
如下操作都是目前讀取:
select lock in share mode (共享鎖定)
select for update (排他鎖)
update (排他鎖)
#insert (排他鎖)
delete (排他鎖定)
序列化交易隔離等級
快照讀取的實作是基於多版本
並發控制,即MVCC,既然是多版本,那麼快照讀取讀到的數據不一定是當前最新的數據,有可能是先前歷史版本
的數據。
如下操作是快照讀:
MVCCC
是「維持一個資料的多個版本,讓讀寫操作沒有衝突」的一個抽象概念
。
這個概念需要具體功能去實現,這個具體實作就是快照讀
。 (具體實現下面講)#
貼心老哥的講解,是不是瞬間
茅廁頓開。
#讀-讀:不存在任何問題,也不需要並發控制
讀-寫:有執行緒安全性問題,可能會造成事務隔離性問題,可能遇到髒讀,幻讀,不可重複讀取
寫入-寫入:有執行緒安全問題,可能會有更新遺失問題,例如第一類更新遺失,第二類更新遺失
單向增長的
時間戳。為每個資料修改保存一個
版本,版本與交易時間戳記
相關聯。
只讀取該交易
開始前的
資料庫快照。
解決問題如下:
並發讀-寫入時:可以做到讀取操作不阻塞寫入操作,同時寫入操作也不會阻塞讀取操作。
髒讀、
幻讀、
不可重複讀取等交易隔離問題,但無法解決上面的
寫-寫更新丟失問題。
因此有了下面提高並發性能的組合拳:
MVCC 悲觀鎖定:MVCC解決讀寫衝突,悲觀鎖定解決寫寫衝突
MVCC 樂觀鎖定:MVCC解決讀寫衝突,樂觀鎖定解決寫寫衝突
版本鏈,
undo日誌 ,
Read View 來實現的
隱藏字段,得開
天眼才能看見。分別是
db_trx_id、
db_roll_pointer、
db_row_id。
6byte,最近修改(修改/插入)事務ID
:記錄建立
這條記錄/最後一次修改
該記錄的交易ID
。
db_roll_pointer(版本鏈關鍵)
7byte,回滾指標
,指向這條記錄
的上一個版本
(儲存在rollback segment裡)
db_row_id
6byte,隱含的自增ID
(隱藏主鍵) ,如果資料表沒有主鍵
,InnoDB會自動以db_row_id產生一個叢集索引
。
實際上還有一個刪除flag
隱藏欄位, 記錄被更新
或刪除
不代表真的刪除,而是刪除flag
變了
#如上圖,db_row_id
#是資料庫預設為此行記錄產生的唯一隱式主鍵
,db_trx_id
是目前操作該記錄的交易ID
,而db_roll_pointer
是一個回滾指標
,用來配合undo日誌
,指向上一個舊版
。
每次對資料庫記錄進行更改,都會記錄一條undo日誌
,每個undo日誌也都有一個roll_pointer
屬性(INSERT操作對應的undo日誌沒有這個屬性,因為該記錄並沒有更早的版本),可以將這些undo日誌都連起來
,串成一個鍊錶
,所以現在的情況就像下圖一樣:
對該記錄每次更新後,都會將舊值放到一個undo日誌中,就算是該記錄的一個舊版本,隨著更新次數的增多,所有的版本都會被roll_pointer
屬性連接成一個鍊錶
,我們把這個鍊錶稱為版本鏈
,版本鏈的頭節點就是目前記錄最新的值。另外,每個版本中還包含產生該版本時對應的事務id,這個資訊很重要,在根據ReadView判斷版本可見性的時候會用到。
Undo log 主要用於記錄
資料被修改之前
的日誌,在表格資訊修改之前先會把資料拷貝到undo log
裡。
當交易
進行回溯時
可以透過undo log 裡的日誌進行資料還原
。
Undo log 的用途
保證交易
進行rollback
時的原子性和一致性
,當交易進行回溯
的時候可以用undo log的資料進行恢復
。
用於MVCC快照讀取
的數據,在MVCC多版本控制中,透過讀取undo log
的歷史版本資料
可以實作不同交易版本號
都擁有自己獨立的快照資料版本
。
undo log主要分為兩種:
#insert undo log
代表交易在insert新記錄時產生的undo log , 只在交易回滾時需要,並且在事務提交後可以被立即丟棄
update undo log(主要)
#交易在進行update或delete時產生的undo log ; 不僅在交易回滾時需要,在快照讀取時也需要;
所以不能隨便刪除,只有在快速讀取或事務回滾不涉及該日誌時,對應的日誌才會被purge執行緒統一清除
交易進行快照讀取
操作的時候生產的讀取視圖
(Read View),在該交易執行的快照讀取的那一刻,會產生資料庫系統目前的一個快照
。
記錄並維護系統目前活躍事務的ID
(沒有commit,當每個事務開啟時,都會被分配一個ID, 這個ID是遞增的,所以越新的事務, ID值越大),是系統中目前不應該被本交易
看到的其他交易id清單
。
Read View主要是用來做可見性
判斷的, 即當我們某個事務
執行快照讀
的時候,對該記錄建立一個Read View讀視圖,把它比作條件用來判斷當前事務
能夠看到哪個版本
的數據,既可能是當前最新
的數據,也有可能是該行記錄的undo log裡面的某個版本
的數據。
Read View幾個屬性
#trx_ids
: 目前系統活躍(未提交
)事務版本號集合。
low_limit_id
: 建立目前read view 時「目前系統最大交易版本號
1」。
up_limit_id
: 建立目前read view 時「系統正處於活躍交易最小版本號
」
creator_trx_id
: 建立目前read view的交易版本號;
db_trx_id
< up_limit_id
|| db_trx_id
== creator_trx_id
(顯示)
如果資料事務ID小於read view中的最小活躍事務ID
,則可以肯定該資料是在目前事務啟動之前
就已經存在
了的,所以可以顯示
。
或資料的事務ID
等於creator_trx_id
,那麼說明這個資料就是目前事務自己產生的
,自己產生的資料自己當然能看見,所以這種情況下此數據也是可以顯示
的。
db_trx_id
>= low_limit_id
(不顯示)
如果資料事務ID大於read view 中的目前系統的最大交易ID
,則表示該資料是在目前read view 建立之後才產生
的,所以資料不顯示
。如果小於則進入下一個判斷
db_trx_id
是否在活躍交易
(trx_ids)中
#不存在
:則說明read view產生的時候交易已經commit
了,這種情況資料可以顯示
。
已存在
:則代表我Read View生成時刻,你這個事務還在活躍,還沒有Commit,你修改的數據,我當前事務也是看不見的。
上面所講的Read View
用於支援RC
(Read Committed,讀取提交)和RR
(Repeatable Read,可重複讀取)隔離等級
的實作
。
RC
隔離等級下,是每個快照讀取
都會生成並且取得最新
的Read View
;
而在RR
隔離層級下,則是同一個交易中
的第一個快照讀取
才會建立Read View
, 之後的
快照讀取所取得的都是同一個Read View
,之後的查詢就不會重複產生
了,所以一個交易的查詢結果每次都是一樣的
。
快照讀
:透過MVCC來控制的,不用加鎖。依照MVCC中規定的「文法」進行增刪改查等操作,以避免幻讀。
目前讀取
:透過next-key鎖定(行鎖 gap鎖定)來解決問題的。
#在RR層級下的某個交易的對某筆記錄的第一次快照讀取會創建一個快照及Read View, 將當前系統活躍的其他事務記錄起來,此後在調用快照讀的時候,還是使用的是同一個Read View,所以只要當前事務在其他事務提交更新之前使用過快照讀,那麼之後的快照讀使用的都是同一個Read View,所以對之後的修改不可見;
即RR等級下,快照讀產生Read View時,Read View會記錄此時所有其他活動事務的快照,這些事務的修改對於目前事務都是不可見的。而早於Read View創建的事務所所做的修改均是可見
而在RC級別下的,事務中,每次快照讀取都會新生成一個快照和Read View,這就是我們在RC層級下的事務中可以看到別的事務提交的更新的原因
從以上的描述我們可以看出來,所謂的MVCC指的就是在使用READ COMMITTD
、REPEATABLE READ
這兩個隔離等級的交易在執行普通的SEELCT
操作時存取記錄的版本鏈
的過程,這樣子可以使不同事務的讀-寫
、寫-讀
操作並發執行
,從而提升系統性能
。
#想了解更多程式設計學習,請關注php培訓欄位!
以上是全網最全的一篇資料庫MVCC詳解,不全我負責的詳細內容。更多資訊請關注PHP中文網其他相關文章!