MVCC多版本併發控制!首先聲明,MySQL的測試環境是5.7sql
前提概要數據庫
什麼是MVCC編程
什麼是當前讀和快照讀?安全
當前讀,快照讀和MVCC的關係併發
MVCC實現原理編程語言
隱式字段ide
undo日誌高併發
Read View(讀視圖)性能
總體流程學習
MVCC相關問題
RR是如何在RC級的基礎上解決不可重複讀的?
RC,RR級別下的InnoDB快照讀有什麼不一樣?
前提概要
什麼是MVCC?
MVCC,全稱Multi-Version Concurrency Control,即多版本併發控制。MVCC是一種併發控制的方法,通常在數據庫管理系統中,實現對數據庫的併發訪問,在編程語言中實現事務內存。
MVCC在MySQL InnoDB中的實現主要是爲了提升數據庫併發性能,用更好的方式去處理讀-寫衝突,作到即便有讀寫衝突時,也能作到不加鎖,非阻塞併發讀
什麼是當前讀和快照讀?
在學習MVCC多版本併發控制以前,咱們必須先了解一下,什麼是MySQL InnoDB下的當前讀和快照讀?
當前讀
像select 語句 lock in share mode(共享鎖), select 語句 for update ;
update, insert ,delete(排他鎖)這些操做都是一種當前讀,爲何叫當前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其餘併發事務不能修改當前記錄,會對讀取的記錄進行加鎖
快照。
像不加鎖的select * from 操做就是快照讀,即不加鎖的非阻塞讀,不涉及其餘鎖之間的衝突;快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當前讀;之因此出現快照讀的狀況,是基於提升併發性能的考慮,快照讀的實現是基於多版本併發控制,即MVCC,能夠認爲MVCC是行鎖的一個變種,但它在不少狀況下,避免了加鎖操做,下降了開銷;既然是基於多版本,即快照讀可能讀到的並不必定是數據的最新版本,而有多是以前的歷史版本
說白了MVCC就是爲了實現讀(select)-寫衝突不加鎖,而這個讀指的就是快照讀, 而非當前讀,當前讀其實是一種加鎖的操做,是悲觀鎖的實現。
當前讀,快照讀和MVCC的關係
準確的說,MVCC多版本併發控制指的是 「維持一個數據的多個版本,使得讀寫操做沒有衝突」 這麼一個概念。僅僅是一個理想概念
而在MySQL中,實現這麼一個MVCC理想概念,咱們就須要MySQL提供具體的功能去實現它,而快照讀就是MySQL爲咱們實現MVCC理想模型的其中一個具體非阻塞讀功能。而相對而言,當前讀就是悲觀鎖的具體功能實現
要說的再細緻一些,快照讀自己也是一個抽象概念,再深刻研究。MVCC模型在MySQL中的具體實現則是由 3個隱式字段,undo日誌 ,Read View 等去完成的,具體能夠看下面的MVCC實現原理
MVCC能解決什麼問題,好處是?
數據庫併發場景有三種,分別爲:
讀-讀:不存在任何問題,也不須要併發控制
讀-寫:有線程安全問題,可能會形成事務隔離性問題,可能遇到髒讀,幻讀,不可重複讀
寫-寫:有線程安全問題,可能會存在更新丟失問題,好比第一類更新丟失,第二類更新丟失
MVCC帶來的好處是?
多版本併發控制(MVCC)是一種用來解決讀-寫衝突的無鎖併發控制,也就是爲事務分配單向增加的時間戳,爲每一個修改保存一個版本,版本與事務時間戳關聯,讀操做只讀該事務開始前的數據庫的快照。
因此MVCC能夠爲數據庫解決如下問題:
在並發讀寫數據庫時,能夠作到在讀(select)操做時不用阻塞寫操做,寫操做也不用阻塞讀操做,提升了數據庫併發讀寫的性能
同時還能夠解決髒讀,幻讀,不可重複讀等事務隔離問題,但不能解決更新丟失問題
總之,MVCC就是由於大牛們,不滿意只讓數據庫採用悲觀鎖這樣性能不佳的形式去解決讀-寫衝突問題,而提出的解決方案,因此在數據庫中,由於有了MVCC,因此咱們能夠造成兩個組合:
MVCC + 悲觀鎖
MVCC解決讀寫衝突,悲觀鎖解決寫寫衝突
MVCC + 樂觀鎖
MVCC解決讀寫衝突,樂觀鎖解決寫寫衝突
這種組合的方式就能夠最大程度的提升數據庫併發性能,並解決讀寫衝突,和寫寫衝突致使的問題
MVCC的實現原理:
MVCC的目的就是多版本併發控制,在數據庫中的實現,就是爲了解決讀寫衝突,它的實現原理主要是依賴記錄中的 3個隱式字段,undo日誌 ,Read View 來實現的。因此咱們先來看看這個三個point的概念:
隱式字段
每行記錄除了咱們自定義的字段外,還有數據庫隱式定義的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
DB_TRX_ID
6byte,最近修改(修改/插入)事務ID:記錄建立這條記錄/最後一次修改該記錄的事務ID
DB_ROLL_PTR
7byte,回滾指針,指向這條記錄的上一個版本(存儲於rollback segment裏)
DB_ROW_ID
6byte,隱含的自增ID(隱藏主鍵),若是數據表沒有主鍵,InnoDB會自動以DB_ROW_ID產生一個聚簇索引
如上圖,DB_ROW_ID是數據庫默認爲該行記錄生成的惟一隱式主鍵,DB_TRX_ID是當前操做該記錄的事務ID,而DB_ROLL_PTR是一個回滾指針,用於配合undo日誌,指向上一個舊版本
undo log日誌主要分爲兩種:
insert undo log
表明事務在insert新記錄時產生的undo log, 只在事務回滾時須要,而且在事務提交後能夠被當即丟棄
update undo log
事務在進行update或delete時產生的undo log; 不只在事務回滾時須要,在快照讀(select,當讀的過程當中有寫的十事務開始和提交,會形成讀數據的髒讀、不可重複讀、幻讀等)時也須要;因此不能隨便刪除,只有在快速讀或事務回滾不涉及該日誌時,對應的日誌纔會被purge線程統一清除。
purge
從前面的分析能夠看出,爲了實現InnoDB的MVCC機制,更新或者刪除操做都只是設置一下老記錄的deleted_bit,並不真正將過期的記錄刪除。
爲了節省磁盤空間,InnoDB有專門的purge線程來清理deleted_bit爲true的記錄。爲了避免影響MVCC的正常工做,purge線程本身也維護了一個read view(這個read view至關於系統中最老活躍事務的read view);若是某個記錄的deleted_bit爲true,而且DB_TRX_ID相對於purge線程的read view可見,那麼這條記錄必定是能夠被安全清除的。
對MVCC有幫助的實質是update undo log ,undo log實際上就是存在rollback segment中舊記錄鏈,它的執行流程以下:
1、 好比一個有個事務插入persion表插入了一條新記錄,記錄以下,name爲Jerry, age爲24歲,隱式主鍵是1,事務ID和回滾指針,咱們假設爲NULL
2、 如今來了一個事務1對該記錄的name作出了修改,改成Tom
在事務1修改該行(記錄)數據時,數據庫會先對該行加排他鎖
而後把該行數據拷貝到undo log中,做爲舊記錄,既在undo log中有當前行的拷貝副本
拷貝完畢後,修改該行name爲Tom,而且修改隱藏字段的事務ID爲當前事務1的ID, 咱們默認從1開始,以後遞增,回滾指針指向拷貝到undo log的副本記錄,既表示個人上一個版本就是它
事務提交後,釋放鎖
3、 又來了個事務2修改person表的同一個記錄,將age修改成30歲
在事務2修改該行數據時,數據庫也先爲該行加鎖
而後把該行數據拷貝到undo log中,做爲舊記錄,發現該行記錄已經有undo log了,那麼最新的舊數據做爲鏈表的表頭,插在該行記錄的undo log最前面
修改該行age爲30歲,而且修改隱藏字段的事務ID爲當前事務2的ID, 那就是2,回滾指針指向剛剛拷貝到undo log的副本記錄
事務提交,釋放鎖
從上面,咱們就能夠看出,不一樣事務或者相同事務的對同一記錄的修改,會致使該記錄的undo log成爲一條記錄版本線性表,既事務鏈,undo log的鏈首就是最新的舊記錄,鏈尾就是最先的舊記錄。
Read View(讀視圖)
什麼是Read View,說白了Read View就是事務進行快照讀(select * from)操做的時候生產的讀視圖(Read View),在該事務執行的快照讀的那一刻,會生成事務系統當前的一個快照,記錄並維護系統當前活躍事務(未提交事務)的ID(當每一個事務開啓時,都會被分配一個ID, 這個ID是遞增的,因此最新的事務,ID值越大)。
因此咱們知道 Read View主要是用來作可見性判斷的, 即當咱們某個事務執行快照讀的時候,對該記錄建立一個Read View讀視圖,把它比做條件用來判斷當前事務可以看到哪一個版本的數據,既多是當前最新的數據,也有多是該行記錄的undo log裏面的某個版本的數據。
ReadView中主要包含4個比較重要的內容:
read view中活躍就是指未提交的事務
1. m_ids:表示在生成ReadView時當前系統中活躍的讀寫事務的事務id列表。
2. min_trx_id:表示在生成ReadView時當前系統中活躍的讀寫事務中最小的事務id,也就是m_ids中的最小
值。
3. max_trx_id:表示生成ReadView時系統中應該分配給下一個事務的id值。
4. creator_trx_id:表示生成該ReadView的快照讀操做產生的事務id。
注意max_trx_id並非m_ids中的最大值,事務id是遞增分配的。比方說如今有id爲1, 2, 3這三個事務,之
後id爲3的事務提交了。那麼一個新的讀事務在生成ReadView時, m_ids就包括1和2, min_trx_id的值就是1,
max_trx_id的值就是4。
基於RR可重複讀隔離級別,實現的基本原理
在select讀數據的過程當中,m_ids首次發現未提交的事務信息不會因在查找過程當中其餘事務id提交而把該事務id排除在外,直至查詢到該事務鏈中最後提交的事務
讀已提交的隔離級別
m_ids:保存事務系統中的活躍的事務id,基於m_ids中的id信息在事務鏈中直到查找到非活躍的事務id(已提交的事務,無論事務提交是否在read view生成後),此時就認爲是該事務id查詢的信息
RC隔離級別是在執行sql時生成read view,RR隔離級別是在事務開始生成read view
有了這個ReadView,這樣在訪問某條記錄時,只須要按照下邊的步驟判斷記錄的某個版本是否可見:
1)若是被訪問版本的trx_id屬性值與ReadView中的creator_trx_id值相同,意味着當前事務在訪問它自
己修改過的記錄,因此該版本能夠被當前事務訪問。
2)若是被訪問版本的trx_id屬性值小於ReadView中的min_trx_id值,代表生成該版本的事務在當前事
務生成ReadView前已經提交,因此該版本能夠被當前事務訪問。
3)若是被訪問版本的trx_id屬性值大於ReadView中的max_trx_id值,代表生成該版本的事務在當前事
務生成ReadView後纔開啓,因此該版本不能夠被當前事務訪問。
4)若是被訪問版本的trx_id屬性值在ReadView的min_trx_id和max_trx_id之間,那就須要判斷一下
trx_id屬性值是否是在m_ids列表中,若是在,說明建立ReadView時生成該版本的事務仍是活躍
的,該版本不能夠被訪問;若是不在,說明建立ReadView時生成該版本的事務已經被提交,該版
本能夠被訪問
原文連接:https://blog.csdn.net/SnailMann/article/details/94724197