一文瞭解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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...