硬核萬字圖解 MySQL 表空間、Tables、Index、雙寫緩沖、Redo Log、Undo Log 原理
數據最終要持久化到磁盤,其磁盤架構設計融合了復雜的存儲結構和精巧的機制,本文將深入剖析其核心模塊的設計原理,并通過圖片輔助理解。
在數據庫領域,MySQL 的 InnoDB 存儲引擎以其高性能、高可靠性和事務支持著稱。
MySQL 到底是怎么管理和存儲各種各樣的數據呢?比如創建一張表、索引、表中的每一行數據、查詢過程中臨時存儲的數據都存在哪里,又如何管理?
這一切都歸功于 MySQL 的 Tablespaces (表空間)的設計,內容較多,本篇就關于以下類型 Tablespaces (表空間)作用、Tables、Index、Doublewrite Buffer、Redo Log、Undo Log 和實現原理展開:
- Tablespace(表空間)
系統表空間(System Tablespace)
獨立表空間(File-Per-Table Tablespaces)
通用表空間(General Tablespaces)
撤銷表空間(Undo Tablespaces)
臨時表空間(Temporary Tablespaces)
- Tables(表)
- Row Formats 行格式
- 主鍵
- 自增主鍵
- Indexes(索引)
- Doublewrite Buffer(雙寫緩沖)
- 為什么需要雙寫緩沖
- 雙寫緩沖架構設計
- Redo Log(重做日志)
- Undo Log(撤銷日志)
Tablespaces (表空間)
表空間可以看做是 InnoDB 存儲引擎邏輯結構的最高層,所有的數據都存放在表空間中,稱之為表空間(tablespace)。
從物理文件的分類來看:
- 日志文件(Undo Log、Redo Log)。
- 系統表空間(System Tablespace)文件 ibdata1。
- Undo tablespace 。
- 獨立表空間(File-Per-Table Tablespaces)
- 通用表空間(General Tablespaces)
- 臨時表空間文件(Temporary Tablespaces)
所以表空間根據不同的場景也分了多種類型,我分別介紹下……
系統表空間(System Tablespace)
默認配置下會有一個初始大小為 10MB,名為 ibdata1 的文件。該文件就是默認的表空間文件(tablespace file)。
系統表空間是 Change Buffer 的存儲區域。
如果表是在系統表空間而非獨立表空間或通用表空間中創建的,它也可能包含表和索引數據。
增加系統表空間大小的最簡單方法是將其配置為自動擴展。
為此,在 innodb_data_file_path 設置中為最后一個數據文件指定 autoextend 屬性,并重啟服務器。
innodb_data_file_path=ibdata1:10M:autoextend為避免系統表空間過大,可考慮使用獨立表空間或通用表空間存儲數據。
獨立表空間是默認的表空間類型,在創建 InnoDB 表時會隱式使用。
獨立表空間(File-Per-Table Tablespaces)
獨立表空間,顧名思義,就是用戶創建的表空間,如果開啟獨立表空間參數,那么一個表空間會對應磁盤上的一個物理文件,每張表對應一個文件,支持事務獨立管理。
其實表空間文件內部還是組織為更復雜的邏輯結構,自頂向下可分為 segment(段)、extent(區)和 page(頁)。
page 則是表空間數據存儲的基本單位,innodb 將表文件(xxx.ibd)按 page 切分,依類型不同,page 內容也有所區別,最為常見的是存儲數據庫表的行記錄。
表空間下一級稱為 segment。segment 與數據庫中的索引相映射。
Innodb 引擎內,每個索引對應兩個 segment:管理葉子節點的 segment 和管理非葉子節點 segment。
創建索引中很關鍵的步驟便是分配 segment,Innodb 內部使用 INODE 來描述 segment。
segment 的下一級是 extent,extent 代表一組連續的 page,默認為 64 個 page,大小 1MB。
InnoDB 存儲引擎的邏輯存儲結構大致如圖 2 所示。
圖 2
默認情況下 InnoDB 存儲引擎有一個共享表空間 ibdata1,即所有數據都存放在這個表空間內。
如果用戶啟用了參數innodb_file_per_table,則每張表內的數據可以單獨放到一個表空間內。
如果啟用了innodb_file_per_table的參數,需要注意的是每張表的表空間內存放的只是數據、索引和插入緩沖 Bitmap 頁.
其他類的數據,如回滾(undo)信息,插入緩沖索引頁、系統事務信息,二次寫緩沖(Double write buffer)等還是存放在原來的系統表空間內。
通用表空間(General Tablespaces)
通用表空間是一種共享的 InnoDB 表空間,通過 CREATE TABLESPACE 語法創建。
通用表空間提供以下功能:
- 類似于系統表空間,通用表空間是一種共享表空間,能夠存儲多張表的數據。
- 通用表空間在內存占用上可能優于獨立表空間。服務器會在表空間生命周期內將表空間元數據保留在內存中。相較于相同數量的表分散在多個獨立表空間中,更少的通用表空間內存儲多張表能減少表空間元數據的內存消耗。
通用表空間通過 CREATE TABLESPACE 語法創建。
CREATE TABLESPACE tablespace_name
[ADD DATAFILE 'file_name']
[FILE_BLOCK_SIZE = value]
[ENGINE [=] engine_name]通用表空間有什么不足?
通用表空間限制有以下限制:
- 現有的表空間無法更改為通用表空間。
- 不支持創建臨時通用表空間。
- 通用表空間不支持臨時表。
- 不支持將表分區放置在通用表空間中。
- 在復制環境中,如果源和副本位于同一主機上,則不支持使用 ADD DATAFILE 子句,因為這會導致源和副本在同一位置創建同名的表空間,而這是不被支持的。
撤銷表空間(Undo Tablespaces)
MySQL InnoDB 引擎的 Undo Tablespaces(撤銷表空間)是磁盤架構設計中用于管理事務回滾日志(Undo Log)的核心組件。
余彥瑭:InnoDB 引擎的 Undo Tablespaces(撤銷表空間)有啥用?
Undo 日志(Undo Log)主要用于事務異常時的數據回滾,在磁盤上 undo 日志保存在 Undo Tablespaces 中。
- 事務回滾與 MVCC 支持Undo 表空間存儲的 Undo Log 記錄了事務對數據的修改前鏡像,用于:
事務回滾時恢復數據原狀;
實現多版本并發控制(MVCC),支持非鎖定一致性讀。
- 分離系統表空間負載在 MySQL 5.7 之前,Undo Log 默認存儲在系統表空間(ibdata1)中。隨著事務頻繁操作,ibdata1 文件會無限增長且無法自動回收空間。5.7 及更高版本引入獨立 Undo 表空間,通過物理隔離減輕系統表空間壓力,提升性能。
MySQL 8.0 默認創建 2 個 Undo 表空間文件(undo_001 和 undo_002),每個初始大小為 16MB,通過參數 innodb_undo_tablespaces 可調整數量(范圍 2-127),每個文件初始 16MB,支持自動擴展和截斷回收。
余彥瑭:“Undo 表空間的邏輯層級管理是咋樣的?”
回滾段(Rollback Segments):每個 Undo 表空間包含 128 個回滾段(由 innodb_rollback_segments 控制),每個回滾段管理 1024 個 Undo 段(Undo Segments)。
Undo 頁與日志記錄:Undo 段由多個 16KB 的頁組成,按事務類型分為 Insert Undo 段(僅用于回滾)和 Update Undo 段(用于 MVCC),前者事務提交后立即釋放,后者需等待無活躍讀視圖時清除。
通過多 Undo 表空間與回滾段的分區設計,理論上支持高達數萬級并發事務(例如:128 表空間 × 128 回滾段 × 1024 Undo 段)。
如下圖所示。
圖片
關鍵說明:
- 每個 Undo 表空間包含 128 個回滾段
- 每個回滾段管理 1024 個 Undo 段(按事務類型分類)
- Undo 段由 16KB 頁 組成,存儲具體日志記錄
余彥瑭:說說 Undo Log 與 MVCC 的協作機制
Undo Log 與 MVCC 的協作機制如下圖所示:
圖片
運作原理:
- 事務修改前將舊數據寫入 Undo Log
- 讀事務通過 Read View 判斷可見性
- 多版本數據通過 Undo Log 鏈回溯訪問
余彥瑭:“系統表空間與 Undo 表空間存儲有啥區別?”
特性 | Undo 表空間 | 系統表空間(歷史方案) |
存儲內容 | 僅 Undo Log | 數據字典、雙寫緩沖、Undo Log 等混合內容 |
空間管理 | 支持自動截斷,避免文件膨脹 | 無法自動回收,需手動調整或重建 |
性能影響 | 減少 I/O 競爭,提升并發處理能力 | 高頻事務易導致文件過大,性能下降 |
版本支持 | MySQL 5.7+ 默認方案 | MySQL 5.6 及更早版本 |
臨時表空間(Temporary Tablespaces)
InnoDB 臨時表空間分為 會話臨時表空間 和 全局臨時表空間,分別承擔不同角色:
- 會話臨時表空間(Session Temporary Tablespaces)
- 用途:存儲用戶顯式創建的臨時表(CREATE TEMPORARY TABLE)以及優化器生成的內部臨時表(如排序、分組操作)。
- 生命周期:會話斷開時自動截斷并釋放回池,文件擴展名為 .ibt,默認位于 #innodb_temp 目錄。
- 分配機制:首次需要創建磁盤臨時表時,從預分配的池中分配(默認池包含 10 個表空間文件),每個會話最多分配 2 個表空間(用戶臨時表與優化器內部臨時表各一)。
- 全局臨時表空間(Global Temporary Tablespace):
- 用途:存儲用戶臨時表的回滾段(Rollback Segments),支持事務回滾操作。
- 文件配置:默認文件名為 ibtmp1,初始大小 12MB,支持自動擴展,由參數 innodb_temp_data_file_path 控制路徑與屬性。
- 回收機制:服務器重啟時自動刪除并重建,意外崩潰時需手動清理。
Temporary Tablespaces 物理結構
圖片
圖示說明:
- 全局臨時表空間:ibtmp1 存儲用戶臨時表的回滾段
- 會話臨時表空間:#innodb_temp 目錄下預分配 10 個 .ibt 文件池(默認配置)
- 每個會話最多激活 2 個臨時表空間(用戶臨時表 + 優化器內部臨時表)。
會話級臨時表空間生命周期
圖片
關鍵點:
- 首次需要磁盤臨時表時從池中分配
- 會話斷開連接后立即歸還空間
- 文件物理保留但內容截斷(類似內存池機制)
臨時表空間使用查詢流程
前面說過臨時表空間可存儲用戶顯式創建的臨時表(CREATE TEMPORARY TABLE)以及優化器生成的內部臨時表(如排序、分組操作)。
那它的查詢過程是怎樣的呢?
Tables(表)
余彥瑭:“在 MySQL 如何創建一張表?”
InnoDB 表通過 CREATE TABLE 語句創建;例如:
CREATE TABLE t1 (a INT, b CHAR (20), PRIMARY KEY (a)) ENGINE=InnoDB;默認情況下, InnoDB 表創建于每表獨立的表空間中。若要在 InnoDB 系統表空間中創建 InnoDB 表,需在創建表前禁用 innodb_file_per_table 變量。
比如,在數據庫 test 中創建一個表 show_index ,在 mysql 的 dataDirectory 目錄下就回出現一個名為 show_index.ibd 的數據文件。
在單個表的數據文件中,數據就是以多個頁的形式進行排列。MySQL 默認配置下,每 16K,即為一個頁。
InnoDB 表以 B+樹 組織數據,每個表對應一個 聚簇索引(Clustered Index),數據行的物理存儲順序與主鍵順序一致。
若未顯式定義主鍵,InnoDB 會隱式生成一個 6 字節的 Row ID 作為主鍵。
Row Formats 行格式
余彥瑭:表中的每一行數據是怎么存儲的?
表的 InnoDB 行格式決定了其行在磁盤上的物理存儲方式。
InnoDB 支持四種行格式,每種格式具有不同的存儲特性。
支持的行格式包括 REDUNDANT 、 COMPACT 、 DYNAMIC 和 COMPRESSED 。其中, DYNAMIC 行格式為默認格式。
余彥瑭:它們有啥區別?
REDUNDANT 和 COMPACT 行格式支持的最大索引鍵前綴長度為 767 字節,而 DYNAMIC 和 COMPRESSED 行格式則支持 3072 字節的索引鍵前綴長度。
在復制環境中,若源服務器上的 innodb_default_row_format 變量設置為 DYNAMIC ,而副本上設置為 COMPACT ,則以下未明確指定行格式的 DDL 語句在源服務器上執行成功,但在副本上會失敗。
Primary Keys 主鍵
建議為創建的每個表定義一個主鍵。在選擇主鍵列時,應選擇具有以下特征的列:
- 重要的查詢語句使用的列。
- 列不能為空。
- 從不包含重復值的列。
- 一旦插入后極少甚至從不更改值的列。
例如,在包含人員信息的表中,你不會將主鍵設在 (firstname, lastname) 上,因為可能有多個人員擁有相同的姓名,姓名列可能留空,且有時人們會更改姓名。
面對如此多的限制條件,通常沒有明顯的一組列適合作為主鍵,因此你會創建一個帶有數字 ID 的新列,作為主鍵。
最好的方式就是使用趨勢遞增的數字作為主鍵。
你也可以 在 InnoDB 表中使用 AUTO_INCREMENT 的列來定義主鍵自動生成。
AUTO_INCREMENT 實現原理是什么?會鎖全表碼?
自增鎖模式通過 innodb_autoinc_lock_mode 變量在啟動時配置。
自增主鍵鎖
“傳統”鎖模式
innodb_autoinc_lock_mode = 0 (“傳統”鎖模式),所有“INSERT 類”語句在向具有 AUTO_INCREMENT 列的表中插入時都會獲得一個特殊的表級 AUTO-INC 鎖。
此鎖通常保持到語句的末尾(而不是事務的末尾),以確保在給定的 INSERT 語句序列中自動增量值按可預測和可重復的順序分配,并確保任何給定語句分配的自動增量值是連續的。
“連續”鎖模式
innodb_autoinc_lock_mode = 1 (“連續”鎖模式),“批量插入”使用特殊的 AUTO-INC 表級鎖,并保持到語句結束。這適用于所有 INSERT ... SELECT 、 REPLACE ... SELECT 和 LOAD DATA 語句。
這種鎖模式確保,在存在 INSERT 語句且行數未知(并且自增值在語句執行過程中分配)的情況下,任何“ INSERT -類似”語句分配的所有自增值都是連續的,并且操作對基于語句的復制是安全的。
innodb_autoinc_lock_mode = 2 (“交錯”鎖模式)
在這種鎖模式中,沒有“ INSERT -like”語句使用表級 AUTO-INC 鎖,并且多個語句可以同時執行。
這是最快且最可擴展的鎖模式,但在使用基于語句的復制或從二進制日志重放 SQL 語句的恢復場景時是不安全的。
在此鎖定模式下,自動增量值在整個并發執行的“ INSERT -like”語句中保證是唯一的且單調遞增。
然而,由于多個語句可以同時生成數字(即,數字的分配在語句之間交錯進行),任何給定語句插入的行生成的值可能不是連續的。
Indexes(索引)
InnoDB 的索引分為 聚簇索引 和 二級索引(Secondary Index),均采用 B+樹結構:
- 聚簇索引:也稱 Clustered Index。是指關系表記錄的物理順序與索引的邏輯順序相同。由于一張表只能按照一種物理順序存放,一張表最多也只能存在一個聚集索引。葉子節點直接存儲行數據。
- 二級索引:也叫 Secondary Index。指的是非葉子節點按照索引的鍵值順序存放,葉子節點存放索引鍵值以及對應的主鍵鍵值。MySQL 里除了 INNODB 表主鍵外,其他的都是二級索引。葉子節點存儲主鍵值,需通過主鍵回表查詢數據。
下圖是一個聚集索引的 B+ Tree 圖。
圖片
1 個 B+ Tree Node,占據一個頁。
- 在索引頁,頁的主要記錄部分(User Records)存放的Record = record header + index key + page pointer。
- 在數據頁,則是按表創建時的row_format類型存放完整數據行記錄。 row_format 類型分別有:Compact、Redundant、Compressed和Dynamic。
因此,在聚集索引中,非葉子節點都為索引頁,葉子節點為數據頁;
在輔助索引中,非葉子節點和葉子節點都為索引頁。不同的是,葉子節點里記錄的是聚集索引中的主鍵 ID 值。
INNODB 表的二級索引,如下圖所示,圖片來自「一樹一溪」:
注意,在索引頁的 Record 中的page pointer,指向的是頁,而非具體的記錄行。
并且 Record 的index key,為指向的 page records 的起始鍵值。
如果主鍵較長,二級索引會占用更多空間,因此擁有較短的主鍵是有利的。
在表空間文件的一個頁的結構上,內容布局為:
圖片
在聚集索引中,數據頁內除了按照主鍵大小進行記錄存放以外,在File header中,有兩個字段:fil_page_prev 和fil_page_next, 分別記錄了上一頁/下一頁的偏移量(offset),用以實現數據頁在 B+ Tree 葉子位置的雙向鏈表結構。
數據如何被查找檢索呢?
通過 B+ Tree 結構,可以明顯看到,通過 B+ Tree 查找,可以定位到索引最后指向的數據頁,并不能找到具體的記錄本身。
這時,數據庫會將該頁加載到內存中,然后通過Page Directory進行二分查找。
余彥瑭:“索引使用單調遞增和 UUID 有什么區別嗎?”
這個問題問的好,我們一定要杜絕使用 UUID 生成的數據作為索引。
順序主鍵(如自增 ID)插入時,數據頁填充率高,減少頁分裂。我們根據上文知道,索引是有序排列的一個 B+tree,單調遞增天然有序,這樣才能高效的使用索引查詢數據。
什么是覆蓋索引優化?
-- 示例表結構
CREATETABLEusers (
idINT PRIMARY KEY,
nameVARCHAR(50),
age INT,
INDEX idx_name_age (name, age)
);
-- 覆蓋索引查詢
SELECTid, name, age FROMusersWHEREname = 'Alice';- 原理:查詢字段全部包含在二級索引中時,無需回表。
- 執行計劃:Extra 列顯示 Using index。
余彥瑭:“排序索引(Sorted Indexes)是什么?”
B+ 樹有序性:所有索引(聚簇/二級)均按索引鍵值排序存儲,支持高效范圍查詢和排序操作。
頁內排序:單個數據頁內的記錄按主鍵順序存儲,頁之間通過雙向鏈表連接。
所以我們么可以使用索引看來優化排序查詢。
-- 利用索引排序
SELECT * FROM users ORDER BY id DESC LIMIT 10;避免 Filesort:若 ORDER BY 子句與索引順序一致,執行計劃顯示 Using index。
Doublewrite Buffer (雙寫緩沖)
InnoDB 是 MySQL 中一種常用的事務性存儲引擎,它具有很多優秀的特性。
其中,Doublewrite Buffer 是 InnoDB 的一個重要特性之一。
為什么需要 Doublewrite Buffer
InnoDB 頁大小為 16KB,而操作系統(如 Linux)頁大小為 4KB,單次寫入需拆分 4 個 OS 頁。
可以使用如下命令查看 MySQL 的 Page 大小:
SHOW VARIABLES LIKE 'innodb_page_size';而 MySQL 程序是跑在 Linux 操作系統上的,MySQL 中一頁數據刷到磁盤,要寫 4 個文件系統里的頁。
圖片
需要注意的是,這個操作并非原子操作,比如我操作系統寫到第二個頁的時候,Linux 機器斷電了,這時候就會出現問題了。
造成”頁數據損壞“。并且這種”頁數據損壞“靠 redo 日志是無法修復的。
Redo log 中記錄的是對頁的物理操作,而不是頁面的全量記錄,而如果發生 partial page write(部分頁寫入)問題時,出現問題的是未修改過的數據,此時重做日志(Redo Log)無能為力。
Doublewrite Buffer 的出現就是為了解決上面的這種情況,雖然名字帶了 Buffer,但實際上 Doublewrite Buffer 是內存+磁盤的結構。
Doublewrite Buffer 是一種特殊文件 flush 技術,帶給 InnoDB 存儲引擎的是數據頁的可靠性。
它的作用是,在把頁寫到磁盤數據文件之前,InnoDB 先把它們寫到一個叫 doublewrite buffer(雙寫緩沖區)的共享表空間內,在寫 doublewrite buffer 完成后,InnoDB 才會把頁寫到數據文件的適當的位置。
如果在寫頁的過程中發生意外崩潰,InnoDB 在稍后的恢復過程中在 doublewrite buffer 中找到完好的 page 副本用于恢復。
架構設計
Doublewrite Buffer 采用 內存+磁盤雙層結構,關鍵組件如下:
內存結構
- 容量固定為 128 個頁(2MB),每個頁 16KB。
- 數據頁刷盤前,通過 memcpy 拷貝至內存 Doublewrite Buffer。
磁盤結構
- 位于系統表空間(ibdata),分為 2 個區(extent1/extent2),共 2MB。
- 數據以 順序寫 方式寫入,避免隨機 I/O 開銷。
工作流程如下圖所示:
圖片
如上圖所示,當有數據修改且頁數據要刷盤時:
- 第一步:記錄 Redo log。
- 第二步:臟頁從 Buffer Pool 拷貝至內存中的 Doublewrite Buffer。
- 第三步:Doublewrite Buffer 的內存里的數據頁,會 fsync 刷到 Doublewrite Buffer 的磁盤上,分兩次寫入磁盤共享表空間中(連續存儲,順序寫,性能很高),每次寫 1MB;
- 第四步:Doublewrite Buffer 的內存里的數據頁,再刷到數據磁盤存儲 .ibd 文件上(離散寫);
時序圖如下:
圖片
崩潰恢復
如果第三步前,發生了崩潰,可以通過第一步記錄的 Redo log 來恢復。
如果第三步完成后發生了崩潰, InnoDB 存儲引擎可以從共享表空間中的 Double write 中找到該頁的一個副本,將其復制到獨立表空間文件,再應用 Redo log 恢復。
在正常的情況下,MySQL 寫數據頁時,會寫兩遍到磁盤上,第一遍是寫到 doublewrite buffer,第二遍是寫到真正的數據文件中,這就是“Doublewrite”的由來。
Doublewrite Buffer 通過 兩次寫 機制,在內存和磁盤間構建冗余副本,成為 InnoDB 保障數據完整性的基石。
其架構設計平衡了性能與可靠性,尤其在高并發或異常宕機場景下表現突出。
Redo Log (重做日志)
重新回顧下 MySQL InnoDB 的內存和磁盤架構設計圖。
我們的目光是關注點在于左側內存架構的 Log Buffer 以及右側磁盤架構的 Redo Log 文件。
圖 1
余彥瑭:Redo Log 有啥用呢?
姐姐你可知道,在數據庫系統中,持久性(Durability) 是事務 ACID 特性的核心要求之一。
其核心問題是:如何確保提交的事務在崩潰后不丟失? 直接修改磁盤數據頁的隨機 I/O 性能低下,且無法保證崩潰瞬間數據的完整性。
InnoDB 的解決方案是引入 Redo Log(重做日志),通過順序寫日志 + 內存緩沖的組合設計實現高性能的持久化保障。
余彥瑭:“說說看 Redo Log 如何保證已提交的事務不丟失”
當數據庫意外崩潰時,如何保證已提交事務不丟失?InnoDB 通過 WAL(Write-Ahead Logging)機制 解決這一核心問題,其實現依賴兩大核心組件:
- Log Buffer:內存中的日志緩沖區
- Redo Log:磁盤上的順序寫日志文件通過二者的協同,InnoDB 在保證 ACID 持久性的同時,將隨機寫轉換為順序寫,實現性能與可靠性的完美平衡。
圖片
Log Buffer 是一個內存層的環形緩沖區。
圖片
關鍵字段說明:
- buf:指向環形緩沖區的內存地址
- write_lsn:原子變量,實現多線程無鎖寫
- hdr_no:塊序號(用于崩潰恢復時定位日志位置)
余彥瑭:“李老師,當事務生成 Redo 記錄后,關鍵步驟有哪些?”
當事務生成 Redo Record 后:
/* 源碼路徑:storage/innobase/log/log0buf.cc */
void log_buffer_write(log_t& log, byte* record, size_t len) {
// 1. 獲取互斥鎖(短時鎖)
mutex_enter(&log.mutex);
// 2. 分配連續空間(跨塊處理)
lsn_t start_lsn = log.assign_lsn(len);
// 3. 復制日志到緩沖區
memcpy(log.buf + write_offset, record, len);
// 4. 無鎖更新 write_lsn
log.update_write_lsn(start_lsn + len);
// 5. 喚醒刷盤線程
os_event_set(log.flusher_event);
}WAL 機制全流程如下圖所示:
圖片
Undo Logs (撤銷日志)
MySQL InnoDB 引擎的事務隔離性由鎖來實現。原子性、一致性、持久性通過數據庫的 redo log 和 undo log 來完成。
余彥瑭:“Undo Log 的本質作用是什么?”
Undo Log 是 InnoDB 實現事務 原子性(Atomicity) 和 多版本并發控制(MVCC) 的核心組件,主要解決兩大關鍵問題:
- 事務回滾:允許事務失敗時恢復到修改前的狀態(原子性)
- 讀一致性:提供非鎖定讀取(Non-Locking Read)的歷史版本(MVCC)
與 Redo Log 形成鮮明對比:
特性 | Redo Log | Undo Log |
目的 | 保證持久性 | 保證原子性和隔離性 |
寫入方向 | 順序寫 | 隨機寫(回滾段中) |
存儲內容 | 物理日志(頁修改) | 邏輯日志(行修改前的值) |
生命周期 | 事務提交后保留到檢查點 | 事務提交后保留到無讀視圖引用 |
清理機制 | Checkpoint 截斷 | Purge 線程異步清理 |
當事務修改數據時, Undo Log 如何生成呢?
關鍵代碼邏輯(row_upd_rec_in_place函數):
/* 存儲位置:storage/innobase/row/row0upd.cc */
void row_upd_rec_in_place(...) {
// 1. 創建 Undo Log Record
trx_undo_report_row_operation(...);
// 2. 寫入回滾段
trx_undo_report_update_impl(...);
// 3. 設置行回滾指針
row_upd_rec_set_roll_ptr(...);
}時序圖如下所示:
圖片
余彥瑭:過期 Undo Log 該如何處理呢?
姐姐問得好,Purge 線程負責清理已提交事務的過期 Undo Log。
就這樣,InnoDB 通過三大日志機制構建完整事務系統:
圖片
設計哲學啟示:
- 分層解耦
- Redo 處理物理持久化
- Undo 處理邏輯回滾
- Binlog 處理邏輯復制
- 空間換時間Undo 保留歷史版本換取無鎖讀能力
- 延遲處理藝術Purge 機制避免事務提交時的同步清理開銷
好了,今天就到這。
































