一文瞭解MySQL中的多版本併發控制

来源:https://www.cnblogs.com/Jcloud/archive/2023/04/11/17305144.html
-Advertisement-
Play Games

最近在閱讀《認知覺醒》這本書,裡面有句話非常打動我:通過自己的語言,用最簡單的話把一件事情講清楚,最好讓外行人也能聽懂。希望藉助今天這篇文章,能用大白話說清楚這個相對比較底層和複雜的MVCC機制 ...


作者:京東零售  李澤陽

最近在閱讀《認知覺醒》這本書,裡面有句話非常打動我:通過自己的語言,用最簡單的話把一件事情講清楚,最好讓外行人也能聽懂。

也許這就是大道至簡,只是我們習慣了煩瑣和複雜。

希望藉助今天這篇文章,能用大白話說清楚這個相對比較底層和複雜的MVCC機制。

在開始之前,先拋出一個問題:我們都知道,目前(MySQL 5.6以上)資料庫已普遍使用InnoDB存儲引擎,InnoDB相對於MyISAM存儲引擎其中一個好處就是在資料庫級別鎖和表級別鎖的基礎上支持了行鎖,還有就是支持事務,保證一組資料庫操作要麼成功,要麼失敗。基於此,問題來了,在InnoDB預設隔離級別(可重覆讀)下,一個事務想要更新一行數據,如果剛好有另外一個事務擁有這個行鎖,那麼這個事務就會進入等待狀態。既然進入等待狀態,那麼等到這個事務獲取到行鎖要更新數據的時候,它讀取到的值是什麼呢?

具體的問題見下圖,我們設定有一張表user,初始化語句如下,試想在這樣的場景下,事務A三次查詢的值分別是什麼?

create table `user` (
`id` bigint not null,
`name` varchar(50) default null,
PROMARY KEY (`id`)
) ENGINE = InnoDB;
insert into user(id,name) values (1,'A');

想要把這件事情回答正確,我們先來鋪墊一下基礎知識。

提到事務,首先會想到的就是ACID(Atomic原子性、Consist一致性、Isolate隔離性、Durable持久性),今天我們主要關註隔離性,當有多個事務同時執行發生併發時,資料庫可能會出現臟讀、不可重覆讀和幻讀等問題,為瞭解決這些問題,“隔離級別”這位大哥上場,包含:讀未提交、讀已提交、可重覆讀和串列

但我們都知道,隔離級別越高,執行效率越低。畢竟大哥就是大哥,級別越高,越謹慎,常在河邊走哪能不濕鞋。

我們通過一個例子簡單說一下這四種隔離級別:

• 讀未提交:一個事務還未提交,它的變更就能被其他事務看到。V1為B,V2為B,V3為B。

• 讀已提交:一個事務提交之後,變更結果對其他事務可見。V1為A,V2和V3為B。

• 可重覆讀:一個事務執行過程中看到的數據與事務啟動時一致。V1為A,V2為A,V3為B。

• 串列:不管讀和寫,加鎖就完了,就是乾!V1和V2均為A,V3為B。

事務是怎麼實現的呢?實際上,事務執行時,資料庫會創建一個視圖,讀未提交直接返回最新值,沒有視圖概念;串列是直接加鎖避免併發訪問;讀已提交是在每個SQL語句開始執行時創建的視圖。可重覆讀的視圖是在事務啟動的時候創建的,整個事務都會使用這個視圖。這樣的話,上面四種不同隔離級別下的V1、V2、V3值便對號入座,有了結果。

MySQL是怎麼實現的呢?我們以MySQL預設的可重覆讀隔離級別為例,實際上每條行記錄在更新時都會記錄一條回滾日誌,也就是大家常說的undo log。通過回滾操作,都可以得到前一個狀態的值。假設name值從初始值A被依次更新為B、C、D,我們看一下回滾日誌:

當前值是D,但是在查詢這條記錄的時候,不同時刻啟動的事務會有不同的視圖,看到的值也就不一樣。在視圖1、2、3、4裡面,記錄的name值分別是A、B、C、D。同一條行記錄在資料庫中可以存在多個版本,這就是多版本併發控制(MVCC)。對於視圖1,如果想要將name值回到A,那麼就要依次執行圖中所有回滾操作。

到這裡,你已經接觸到了MVCC的概念,也許你已經對文章最開始的問題有了一點點想法,彆著急,我們先來簡單總結下MVCC的特點:

MVCC的出現使得一條行記錄在不同隔離級別下不同的事務操作會形成一條不同版本的鏈路,從而實現在不加鎖的前提下使不同事務的讀寫操作能夠併發安全執行,這個版本鏈就是通過回滾日誌undo log實現的。用大白話說,你這個事務想要查詢一條行記錄,MVCC會通過你這個事務所在視圖確認版本鏈中哪個版本的行數據對你可見。剛纔我們提到,四種隔離級別下,只有讀已提交可重覆讀會用到視圖。對於讀已提交,MVCC會在每次查詢前都會生成一個視圖,可重覆讀隔離級別隻會在第一次查詢時生成一個視圖,之後在這個事務中的所有查詢操作都會重覆使用這個視圖。行業上,將創建視圖的那一刻稱為快照,晃你一下子,讓你激靈激靈,別發生臟讀,變臟嘍~

想要解決文章最開始的那個問題,我們還得展開說說版本鏈是如何形成的和快照的原理,稍有枯燥,先忍一下,耐心看下去,乖~

對於InnoDB存儲引擎來說,主鍵索引(也稱為聚簇索引)記錄中除了正常的欄位數據外,還包含兩個隱藏列:

(1)trx_id:每次一個事務想要對主鍵索引進行更新、刪除和新增時,都會把這個事務的事務id賦值給trx_id欄位。註意事務id嚴格遞增,且查詢操作不會分配事務id,即trx_id = 0;

(2)roll_point:每次一個事務對主鍵索引進行更新時,都會把舊的版本寫入到undo日誌中,roll_point相當於一個指針,通過它可以找到這條記錄修改前的信息。

我們以可重覆讀隔離級別為例,為了尚未提交的更新結果對其他事務不可見,InnoDB在創建視圖時,有以下四部分組成:

• m_ids:表示生成視圖時,當前系統中“活躍”的讀寫事務的事務id列表,這裡的活躍大白話就是事務尚未提交;

• min_trx_id:表示在生成視圖時,當前系統中活躍的讀寫事務中最小的事務id,即m_ids中的最小值;

• max_trx_id:表示生成視圖時系統應該分配給下一個事務的id值;

• creator_trx_id:表示生成該視圖的事務id。

概念比較多,舉個例子,現在有事務id分別是1、2、3三個事務,1和2事務尚未提交,3事務已提交,這個時候如果來了一個新事務,那麼它創建的視圖對應這幾個參數分別為:m_ids包含1、2,min_trx_id為1,max_trx_id為4。

關鍵的知識點來了,如何根據某個事務生成的視圖,判斷版本鏈上的某個版本對這個事務可見呢?

遵循下麵步驟:

1、版本鏈上的不同版本trx_id值如果與這個視圖的creator_trx_id值相同,說明當前事務在訪問它自己修改過的記錄,所以被訪問的版本對當前事務可見。一家人還是認識一家人的~

2、版本鏈上的不同版本trx_id值小於這個視圖的min_trx_id值,說明這個版本的事務在當前事務生成視圖之前就已經提交了,所以被訪問的版本對當前事務可見。

3、版本鏈上的不同版本的trx_id值大於或等於這個視圖的max_trx_id值,說明這個版本的事務在當前事務之後才開啟,所以被訪問版本對當前事務不可見。

4、版本鏈上的不同版本的trx_id值在這個視圖的min_trx_id和max_trx_id之間,需要進一步判斷被訪問版本trx_id值是不是在m_ids中,如果在,說明當前事務是活躍的,被訪問版本對當前事務不可見。如果不在,說明被訪問版本的事務已經提交了,被訪問版本對當前事務可見。

比較繞是不是,千萬別暈,兄弟呀~,大白話解釋一下,設定某個事務生成的視圖瞬間(也就是快照),這個事務的id為creator_trx_id,那麼有下麵三種可能:

1、如果creator_trx_id落在綠色部分,表示被訪問的版本是已提交的事務或者就是當前事務自己生成的,這個數據是可見的;

2、如果creator_trx_id落在紅色部分,表示被訪問的版本還未開啟,數據不可見;

3、如果creator_trx_id落在黃色部分,包括兩種情況:

若creator_trx_id在m_ids集合中,表示被訪問的版本尚未提交,數據不可見;

若creator_trx_id不在m_ids集合中,表示被訪問的版本已經已經提交了,數據可見。

知道了這個之後,我們就可以回答文章最開始那個問題了,在隔離級別為可重覆讀的情況下(這裡的隱含條件就是可重覆讀隔離級別隻會在第一次查詢時生成一個視圖,之後在這個事務中的所有查詢操作都會重覆使用這個視圖)分析一波:

以文章開頭的例子,設定事務B的事務id=100,事務C的事務id=200,當事務B尚未提交時,id=1這條記錄的版本鏈是這樣的:

這個時候我們看一下事務A第一個select語句,註意查詢操作的事務trx_id=0,在執行select語句時會創建一個視圖,這個視圖的m_ids={100},min_trx_id=100,max_trx_id=101,creator_trx_id=0。

然後在版本鏈中挑選可見的數據記錄,從圖中可以看到最新版本的name值是B,最新版本的trx_id值為100,在m_ids集合中,這個版本數據不可見,根據roll_point跳到下一個版本;

下一個版本的name值是A,這個版本的trx_id=99,小於min_trx_id,這個版本數據是可見的,所以返回name為A的記錄,即V1為A。

我們繼續,事務B這時進行了commit提交,此時事務C已經開啟,那麼事務A第二個select語句不會創建一個新的視圖,而是重新利用第一次創建的視圖。最新版本的trx_id為100,在m_ids中,數據不可見,即V2=A;

接下來,事務C進行了更新操作,此時版本鏈發生的改變如下:

事務C接著進行了commit提交,此時事務A第三次select語句也不會創建一個新的視圖,最新版本的trx_id為200,大於max_trx_id,數據不可見,即V3=A。

到這裡,MVCC就結束啦,留一個小問題,如果是讀已提交隔離級別,那麼文章開頭的例子中V1、V2、V3的值又分別是什麼呢?答案在最後哦。

最後,我們再來總結一下MVCC的作用,使用可重覆讀隔離級別的事務在查詢時,僅會使用第一次select時生成的視圖,相比於讀已提交隔離級別每次查詢都會生成一個新的視圖,可重覆讀在查詢時使用的視圖版本不會那麼新,因此有些已經提交的事務對行記錄進行修改時對查詢事務就不可見,進而避免了不可重覆讀現象的發生,同時也避免了臟讀。


小問題答案:

讀已提交隔離級別下,每次select查詢都會生成一個新的視圖,基於此,分析如下:

事務A第一個select語句,註意查詢操作的事務trx_id=0,在執行select語句時會創建一個視圖,這個視圖的m_ids={100},min_trx_id=100,max_trx_id=101,creator_trx_id=0。

然後在版本鏈中挑選可見的數據記錄,從圖中可以看到最新版本的name值時B,最新版本的trx_id值為100,在m_ids集合中,這個版本數據不可見,根據roll_point跳到下一個版本;

下一個版本的name值是A,這個版本的trx_id=99,小於min_trx_id,這個版本數據是可見的,所以返回name為A的記錄,即V1為A。

事務B這時進行了commit提交,此時事務C已經開啟,那麼事務A第二個select語句會創建一個新的視圖,這個視圖的m_ids={200},min_trx_id=200,max_trx_id=201,creator_trx_id=0。版本鏈沒有發生變化,最新版本trx_id值為100,小於min_trx_id,數據可見,即V2=B;

事務C接著進行了commit提交,此時事務A第三次select語句會創建一個新的視圖,這個視圖的m_ids={},min_trx_id不存在,max_trx_id=201,creator_trx_id=0。在版本鏈中挑選可見的數據記錄,從圖中可以看到最新版本的name值為C,最新版本的trx_id值為200,小於max_trx_id且不在m_ids中,則數據可見,即V3=C。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在一些實際的場景里,我們需要通過利用一些埠轉發工具,比如系統自帶的命令行工具或第三方小軟體,來繞過網路訪問限制觸及目標系統。下文為大家總結了linux系統和windows系統埠轉發常用的一些方法。 ...
  • 在AIX系統中,查看網卡配置可以使用entstat和lsdev命令來實現。 使用entstat命令查看網卡配置 使用以下命令查看所有網卡的信息: entstat -all 使用以下命令查看指定網卡的信息: entstat -d enX 其中enX代表網卡的名稱,例如en0表示第一塊網卡,en1表示第 ...
  • GPFS 文件系統部署步驟 參考文檔: 簡書網友提供: https://www.jianshu.com/p/a0ecc0838b3b?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendat ...
  • 首先可以直接改Hosts文件(現在不太管用了)。如果你是Linux或Mac系統,那麼可以通過命令sudo vim /etc/hosts打開Hosts文件併進行修改。如果你已經擁有了網路代理服務,那麼我們可以在此基礎上配置終端代理。我們查看得知自己代理伺服器的IP地址為http://127.0.0.1... ...
  • 鎖屏面試題百日百刷,每個工作日堅持更新面試題。請看到最後就能獲取你想要的,接下來的是今日的面試題: 1.請說明什麼是Apache Kafka? Apache Kafka是由Apache開發的一種發佈訂閱消息系統,它是一個分散式的、分區的和重覆的日誌服務。 2.請說明什麼是傳統的消息傳遞方法? 傳統的 ...
  • 解壓flume包 到/usr/local/src/目錄下 [root@hadoopha01 pack]# tar -zxvf apache-flume-1.7.0-bin.tar.gz -C /usr/local/src/ 配置flume環境變數 #FLUME_HOME export FLUME_H ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者: Yejinrong/葉金榮 文章來源:GreatSQL社區投稿 編譯GreatSQL 安裝gdb 開始調試GreatSQL源碼 3.1 利用gd ...
  • Redis 是一個高性能的鍵值存儲系統,支持多種數據結構。 包含五種基本類型 String(字元串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),和三種特殊類型 Geo(地理位置)、HyperLogLog(基數統計)、Bitmaps(點陣圖)。 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...