你一定要知道的MySQL之MVCC多版本併發控制

提到MVCC,那麼首先還是要說一下什麼是事務和事務隔離級別

MySQL事務

在關係型數據庫中,一個邏輯工作單元要成爲事務,必須滿足四個特性,ACID,即原子性(Atomicity),一致性(Consistency),隔離性(Isolation)和持久性(Durability)

原子性

事務是一個原子操作單元,其對數據的修改,要麼全部提交,要麼全部回滾。

一致性

指的是事務開始之前和事務結束之後,數據庫的完整性限制未被破壞。一致性包括兩個方面的內容,分別是約束一致性和數據一致性。

  • 約束一致性:創建表結構時所指定的外鍵,Check,唯一索引等約束,在MySQL中不支持Check
  • 數據一致性:是一個綜合性的規定,因爲它是由原子性,持久性,隔離性共同保證的結果,而不是單單依賴於某一種技術。

隔離性

指的是事務之間相互不能干擾,即一個事務內部的操作以及使用的數據對其他併發事務是隔離的。

持久性

指的是一個事務一但提交了,它對數據庫中的數據的改變就應該是永久性的,後續的操作和故障不應該對其有任何影響,不會丟失。

併發事務

當事務進行併發處理的時候,就可能會帶來一些問題,比如:更新丟失,髒讀,不可重複讀,幻讀等。

  • 更新丟失:當兩個或多個事務更新同一行記錄,會產生更新丟失現象。可以分爲回滾覆蓋和提交覆蓋。
    • 回滾覆蓋:一個事務回滾操作,把其他事務已提交的數據給覆蓋了
    • 提交覆蓋:一個事務提交操作,把其他事務已提交的數據給覆蓋了
  • 髒讀:一個事務讀取到了另一個事務修改但未提交的數據。
  • 不可重複讀:一個事務中多次讀取同一行記錄不一致,後面讀取的跟前面讀取的不一致
  • 幻讀:一個事務中多次相同條件查詢,結果不一致。後續查詢的結果和前面查詢結果不同,多了或少了幾行記錄

不可重複讀總的來說就是一個事務中多次數據到的記錄內容不同,幻讀總的來說就是就是一個事務中多次查詢總數不同。

事務隔離級別

前面提到的"更新丟失","髒讀","幻讀"等併發事務問題,其實都是數據庫一致性問題,爲了解決這些問題,MySQL數據庫是通過事務隔離級別來解決的,數據庫系統提供了以下4種事務隔離級別供用戶選擇。
  • 讀未提交:解決了回滾覆蓋類型的更新丟失,但可能會發生髒讀現象,也就是可能讀取到其他會話中未提交事務修改的數據。
  • 讀已提交:只能讀取到其他會話中已提交的數據,解決了髒讀。但可能發生不可重複讀現象,也就是可能在一個事務中兩次查詢結果不一致。
  • 可重複讀:解決了不可重複讀,它確保同一事務的多個實例在併發讀取數據時,會看到同樣的數據行。不過理論上會出現幻讀,簡單的說幻讀指的是當用戶讀取某一範圍的數據行時,另一個事務又在該範圍插入了新行,當用戶讀取該範圍的數據時會發現新的幻影行。
  • 串行化:所有的增刪改查串行執行,它通過強制事務排序,解決相互衝突,從而解決幻讀的問題,這個級別可能導致大量的超時現象和鎖競爭,效率低下。

數據庫的事務隔離級別越高,併發問題就越小,但是併發處理能力越差(代價)。讀未提交隔離級別最低,併發問題多,但是併發處理能力好。以後使用時,可以根據系統特點來選擇一個合適的隔離級別,比如對不可重複讀和幻讀並不敏感,更多關心數據庫併發處理能力,此時可以使用Read Commited隔離級別。

下面用一幅圖讓大家理解一下上面說到的隔離級別。假設數據表T中只有一列,其中一行的值是1,下面是按照時間順序執行兩個事物的行爲

我們來看看在不同的隔離級別下,事物A會有哪些不同的返回結果,也就是圖裏面V1,V2,V3的返回值分別是什麼。

若隔離級別是"讀未提交",則V1的值就是2。這時候事物B雖然還沒有提交,但是結果已經被A看到了。因此V2,V3也都是2。

若隔離級別是"讀已提交",則V1是1,V2是的值是2。事物B的更新在提交後才能被A看到,所以,V3的值也是2

若隔離級別是"可重複讀",則V1,V2是1,V3是2。之所有V2還是1,遵循的就是這個要求:事物在執行期間看到的數據前後必須是一致的。

若隔離級別是"串行化",則在事務B執行"將1改成2"的時候,會被鎖住。直到事務A提交後,事務B纔可以繼續執行。所以從A的角度看,V1,V2值是1,V3的值是2。

在MySQL中,默認的事務隔離級別是可重複讀,爲什麼不是串行化呢?上面說到了,串行化就當相當於加了一把鎖,變成同步串行執行了,效率會低的可憐,所以,MySQL中默認的事務隔離級別是可重複讀

select @@TRANSACTION_ISOLATION 用這個命令查看事務的隔離級別,結果

注意:事務隔離級別,針對InnoDB引擎,支持事務功能,MyISAM引擎沒有事務。

Redo和Undo日誌

在講MVCC之前,先講一下Redo和Undo日誌

Undo Log

Undo顧名思義,撤銷,取消的意思,所以這裏的Undo Log其實就是爲了事務的回滾。

數據庫事務開始之前,會將要修改的記錄存放到Undo日誌中,當事務回滾時或者數據庫崩潰時,可以利用Undo日誌,撤銷未提交事務對數據庫產生影響,Undo日誌在事務提交時,並不會立即刪除,innodb會將該事務對應的undo log放入到刪除列表中,後面會通過後臺線程purge thread進行回收處理。Undo log屬於邏輯日誌,記錄一個變化過程,例如,執行一個delete,undo log會記錄一個insert,執行一個update,undo log會記錄一個反的update。

Undo log存儲:undo log採用段的方式管理和記錄。在innodb數據文件中包含一種rollback segment回滾段,內部包含1024個undo log segment。可以通過下面一組參數來控制undo log存儲。

show variables like '%innodb_undo%';

Redo Log

Redo國名思議就是重做。以恢復操作爲目的,在數據庫發生意外時重現操作。

Redo log指事務中修改任何數據,將最新的數據備份存儲的位置(Redo log),被稱爲重做日誌。

Redo log隨着事務操作的執行,就會生成Redo log。在事務提交時會將產生Redo log寫入Log buffer,並不是隨着事務的提交就立刻寫入磁盤文件。等事務操作的髒頁寫入到磁盤之後,Redo log的使命也完成了,Redo log佔用的空間就可以重用(被覆蓋寫入)。

MVCC多版本併發控制

什麼是MVCC?

MVCC-多版本併發控制(Multi-Version Concurrency Control)主要是MySQL當中InnoDB存儲引擎用來實現隔離級別的一種具體實現,用於實現讀已提交和可重複讀這兩種隔離級別。在代碼中,讀已提交和可重複讀是兩個接口,MVCC就是具體實現。

MVCC對一行數據的讀和寫兩個操作默認是不會通過加鎖互斥來保證隔離性,避免頻繁加鎖互斥,而在串行化隔離級別爲了保證較高的隔離性是通過將所有操作加鎖來實現的。

undo日誌版本鏈與read view機制詳情

undo日誌版本鏈是指一行數據被多個事務依次修改過後,在事務修改完後,MySQL會保留修改之前的數據undo回滾日誌,並且用兩個隱藏字段trx_id和roll_pointer把這些undo日誌串聯起來形成一個歷史記錄版本鏈

trx_id是事務id,roll_pointer是用來指向上一個事務地址的回滾指針,簡單來說就是,原本數據庫裏面有一條信息,這時候有個事務來修改這行了,事務id是300,那麼undo就會新增一條行,記錄id,name和事務id300還有就是roll_pointer用來記錄上一個事務記錄的位置

在可重複讀隔離級別,當事務開啓,執行任何查詢sql時會生成當前事務的一致性視圖read-view,該視圖在事務結束之前不會變化(如果是讀已提交隔離級別在每次執行查詢sql時都會重新生成),這個視圖由執行查詢時所有未提交事務id數組(數組裏最小的id爲min_id)和已提交的最大事務id(max_id)組成,事務裏的任何sql查詢結果需要從對應版本鏈裏的最新數據開始逐條跟read-view做比對從而得到最終的快照結果

版本鏈比對規則:

  1. 如果row的trx_id落在綠色部分(trx_id<min_id),表示這個版本是已提交的事務生成的,這個數據是可見的。
  2. 如果row的trx_id落在紅色部分(trx_id>min_id),表示這個版本是由將來啓動的事務生成的,是不可見的(若row的trx_id就是當前自己的事務是可見的)
  3. 如果row的trx_id落在黃色部分(min_id<=trx_id<=max_id),那就包括兩種情況
    1. 若row的trx_id在視圖數組中,表示這個版本是由還沒提交的事務生成的,不可見(若row的trx_id就是當前自己的事務是可見的)
    2. 若row的trx_id不在視圖數組中,表示這個版本是已經提交了的事務生成的,可見。

舉例說明一下: 這裏我們有5個事務

看上面的圖,首先幾個事務同時begin,事務1進行update,緊接着,事務2和3也進行update操作,之後事務3進行commit事務,緊接着事務4進行select查詢,首先按照上面說的,會生成一個當前事務的一致性視圖[100,200], 300 假設生成是這樣的視圖,括號裏面的是所有未提交事務id數組,和一個已經提交的最大事務id。這裏事務4第一次select,查詢結果,相信大家應該已經猜到了,沒錯就是lilei300,這裏首先判斷數組裏面的事務編號是100的,跟trx_id進行比較發現他其實是在上圖中未提交與已提交事務中,這時候發現,trx_id在視圖數組中,是不可見的,然後200進行比較,還是和100一樣,然後用300進行比較,發現300的事務id也在未提交與已提交事務中,但是不在視圖數組中,表示這個班班是已經提交的事務生成的,所以最後返回事務id爲300的name,lilei300,由於生成的視圖是不變的,在可重複讀隔離級別下,所以第二次select和第三次select的結果都是一樣的。但是最後一個事務,是在第一個事務commit之後查詢所以,第一個視圖查詢結果就是lilei2

對於刪除的情況可以認爲是update的特殊情況,會將版本鏈最新的數據複製一份,然後將trx_id修改成刪除操作trx_id,同時在該記錄的頭信息(record header)裏的(deleted_flag)標記位寫上true,來表示當前記錄已經刪除,在查詢時候按照上面的規則查到對應的記錄,如果delete_flag標記位是true,意味着記錄已被刪除,則不返回數據。

注意:begin/start transaction命令並不是一個事務的起點,在執行到他們之後的第一個修改操作InnoDB表的語句,事務才真正啓動,纔會向MySQL申請事務id,mysql內部是嚴格按照事務的啓動順序來分配事務id的。

總結:MVCC機制的實現就是通過read-view機制與undo版本鏈比對機制,使得不同的事務會根據數據版本鏈對比規則讀取同一條數據在版本鏈上的不同版本數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章