定義
MVCC是多版本并發控制 Multi-Version Concurrent Contrl。
它是MySQL中的提高性能的一種方式,配合Undo log 和版本鏈,替代鎖,讓不同事物的讀-寫、寫-讀操作可以并發的執行,從而提升系統的性能。
MVCC 在 MySQL InnoDB 中的實現主要是為了提高數據庫并發性能。一般是在使用讀已提交(PEAD COMMITTED)和可重復讀(REPEATABLE READ)隔離級別的事務中實現。
用自己的話說就是:
多版本意思是指數據庫中一條數據有多個版本同時存在,在某個事務對其進行具體操作的時候,是需要查看這一條記錄的隱藏列事務版本的id,比對事務id并根據事物的隔離級別從而去判斷是哪個版本的數據。
準確的說,MVCC多版本并發控制指的是 “維持一個數據的多個版本,使得讀寫操作沒有沖突” 這么一個概念。
當前讀和快照讀
在了解MVCC之前,還要了解一下,MySQL InnoDB中的當前讀和快照讀。
當前讀
像 select in share mode(共享鎖),select for update,update,insert,delete(排它鎖)這些操作都是一種當前讀,當前讀就是它讀取的是記錄的最新版本的數據。讀取時還要保證其他并發事務不能修改當前的記錄,會對讀取的記錄進行加鎖。
快照讀
就像不加鎖的select操作就是快照讀,即不加鎖的非阻塞讀;
快照讀的前提就是隔離級別不是串行級別,串行級別下的快照讀會退化成當前讀;
之所以出現快照讀的情況,是基于提高并發性能的考慮,快照讀的實現是基于多版本并發控制,即MVCC,可以認為MVCC是行鎖的一個變種,但它在很多情況下都避免了加鎖操作,降低了開銷;
既然是基于多版本,即快照讀可能讀到的并不一定是數據的最新版本,而有可能是之前的歷史版本。
說白了就是MVCC就是為了實現讀-寫沖突不加鎖,而這個讀指的就是快照讀,而非當前讀,當前讀實際上就是一種加鎖的操作,是悲觀鎖的實現。
MVCC的優點
MVCC在MySQL InnoDB中的實現主要是為了提高數據庫的并發性能,用更好的方式去處理讀-寫或寫-讀之間的沖突,也能做到不加鎖,非阻塞并發讀,提高了數據庫并發讀寫的性能。
MVCC還可以解決臟讀,幻讀,不可重復讀等事務隔離問題。但它還不能解決更新丟失的問題。
所以MVCC能夠解決讀-寫之間的并發控制,但它不能解決寫-寫之間的的并發控制
總而言之,MVCC就是后人通過再研究出的一種機制,在早期程序只能采用悲觀鎖這種性能不佳的形式去解決讀-寫 寫-寫之間的沖突問題,為現在有了MVCC我們就可以打組合拳。
現在MVCC + 悲觀鎖 、 MVCC + 樂觀鎖這樣的機制來解決。MVCC解決讀-寫沖突問題,鎖解決寫-寫之間的沖突。
這樣組合下來,最大程度上提高了數據庫的并發執行性能。
基本原理
因為MVCC的目的就是控制并發控制的,在數據庫中的實現,為了解決讀寫的沖突問題。
它的實現原理主要依賴3個模塊:隱藏字段、undo日志、Read View來實現的。
隱藏字段
對于使用 InnoDB 存儲引擎的表來說,它的聚簇索引記錄中都包含兩個必要的隱藏列。
● trx_id:每次對某條聚簇索引記錄進行改動的時候,都會把對應的事務id賦值給trx_id進行記錄的隱藏列。
● roll_pointer:在每次對某條聚簇索引記錄進行改動的時候,都會把舊版本寫入undo日志當中,然后這個隱藏列就相當于一個指針的作用,我們可以通過roll_pointer來找到該記錄修改之前的信息。
undo日志
undo log主要分為兩種:
insert undo log:
代表事務在insert新記錄時產生的undo log,只在事務回滾時需要,并且在事務提交后就立即刪除。
update undo log:
事務在進行update或delete時產生的undo log;不僅在事務回滾的時需要,在快照時也需要;所以不能隨便刪除,只有在快速讀或事務回滾不涉及該日志時,對應的日志才會被purge線程統一清除。
Read View(讀視圖)
對于使用READ UNCOMMITTED(讀未提交)隔離級別的事務來說,直接讀取記錄的最新版本就好了,對于使用SERIALIZABLE(串行化)隔離級別的事務來說,使用加鎖的方式來訪問記錄。
所以在InnoDB引擎中設計了一個ReadView的概念。
Read View就是事務進行快照讀操作的時候產生的讀視圖(Read View),在該事務執行的快照讀的那一刻,會生成數據庫系統當前的一個快照,記錄并維護系統當前活躍事務的ID(當每個事務開啟時,都會分配一個ID,這個ID是自增的,所以最新的事務,ID越大)。
在MySQL當中,READ COMMITTED和REPEATABLE READ 隔離級別的一個非常大的區別就是它們生成的Read View 的時機不同。
● READ COMMITTED:每次讀取數據前都生成一個ReadView;
● REPEATABLE READ:在第一個讀取數據時生成一個ReadView;
版本鏈
前面說到,每次數據更新的時候都更新undo 日志,在undo 日志中就會出來一個版本鏈的情況。
那么它的主要執行流程如下所示:
假設對事務id為1001的行做操作
現在來了一個事務1002 過來修改name為“李四”
在事務1001中修改該行的數據時,數據庫先會對其加排它鎖。
然后把該行數據拷貝到nudo日志中,所為舊記錄。
拷貝完成后,就修改name為“李四”,并且修改事務id為當前事務的id,回滾指針指向副本的記錄
提交事務,釋放鎖
現在再來了一個事務1003 過來修改age為20
在事務1002中修改該行的數據時,數據庫先會對其加排它鎖。
然后把該行數據拷貝到nudo日志中,所為舊記錄。
拷貝完成后,就修改agewei20,并且修改事務id為當前事務的id,回滾指針指向副本的記錄
提交事務,釋放鎖
從上面就可以看出,不同的事務或者相同事務對同一記錄修改的時候,會導致日志中成為一條記錄版本線性表,這個就是版本鏈。
undo日志中鏈首即就是最新的舊記,鏈尾就是最早的舊記錄。
但是在實際當中,會在第一個事務完成提交后,就可能被purge線程刪除丟失,這里為了演示,就留在這了。
為了節省磁盤空間,InnoDB有專門的purge線程來清理undo log中的記錄。為了不影響MVCC的正常工作,purge線程自己也維護了一個read view(這個read view相當于系統中最老活躍事務的read view)
————————————————
版權聲明:本文為CSDN博主「星辰與晨曦」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接://blog.csdn.net/weixin_45970271/article/details/124380044