偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

硬核萬字圖解 MySQL 表空間、Tables、Index、雙寫緩沖、Redo Log、Undo Log 原理

數(shù)據(jù)庫 MySQL
MySQL 到底是怎么管理和存儲(chǔ)各種各樣的數(shù)據(jù)呢?比如創(chuàng)建一張表、索引、表中的每一行數(shù)據(jù)、查詢過程中臨時(shí)存儲(chǔ)的數(shù)據(jù)都存在哪里,又如何管理?

數(shù)據(jù)最終要持久化到磁盤,其磁盤架構(gòu)設(shè)計(jì)融合了復(fù)雜的存儲(chǔ)結(jié)構(gòu)和精巧的機(jī)制,本文將深入剖析其核心模塊的設(shè)計(jì)原理,并通過圖片輔助理解。

在數(shù)據(jù)庫領(lǐng)域,MySQL 的 InnoDB 存儲(chǔ)引擎以其高性能、高可靠性和事務(wù)支持著稱。

MySQL 到底是怎么管理和存儲(chǔ)各種各樣的數(shù)據(jù)呢?比如創(chuàng)建一張表、索引、表中的每一行數(shù)據(jù)、查詢過程中臨時(shí)存儲(chǔ)的數(shù)據(jù)都存在哪里,又如何管理?

這一切都?xì)w功于 MySQL 的 Tablespaces (表空間)的設(shè)計(jì),內(nèi)容較多,本篇就關(guān)于以下類型 Tablespaces (表空間)作用、Tables、Index、Doublewrite Buffer、Redo Log、Undo Log 和實(shí)現(xiàn)原理展開:

  • Tablespace(表空間)

系統(tǒng)表空間(System Tablespace)

獨(dú)立表空間(File-Per-Table Tablespaces)

通用表空間(General Tablespaces)

撤銷表空間(Undo Tablespaces)

臨時(shí)表空間(Temporary Tablespaces)

  • Tables(表)
  • Row Formats 行格式
  • 主鍵
  • 自增主鍵
  • Indexes(索引)
  • Doublewrite Buffer(雙寫緩沖)
  • 為什么需要雙寫緩沖
  • 雙寫緩沖架構(gòu)設(shè)計(jì)
  • Redo Log(重做日志)
  • Undo Log(撤銷日志)

Tablespaces (表空間)

表空間可以看做是 InnoDB 存儲(chǔ)引擎邏輯結(jié)構(gòu)的最高層,所有的數(shù)據(jù)都存放在表空間中,稱之為表空間(tablespace)。

從物理文件的分類來看:

  • 日志文件(Undo Log、Redo Log)。
  • 系統(tǒng)表空間(System Tablespace)文件 ibdata1。
  • Undo tablespace 。
  • 獨(dú)立表空間(File-Per-Table Tablespaces)
  • 通用表空間(General Tablespaces)
  • 臨時(shí)表空間文件(Temporary Tablespaces)

所以表空間根據(jù)不同的場(chǎng)景也分了多種類型,我分別介紹下……

系統(tǒng)表空間(System Tablespace)

默認(rèn)配置下會(huì)有一個(gè)初始大小為 10MB,名為 ibdata1 的文件。該文件就是默認(rèn)的表空間文件(tablespace file)。

系統(tǒng)表空間是 Change Buffer 的存儲(chǔ)區(qū)域。

如果表是在系統(tǒng)表空間而非獨(dú)立表空間或通用表空間中創(chuàng)建的,它也可能包含表和索引數(shù)據(jù)。

增加系統(tǒng)表空間大小的最簡(jiǎn)單方法是將其配置為自動(dòng)擴(kuò)展。

為此,在 innodb_data_file_path 設(shè)置中為最后一個(gè)數(shù)據(jù)文件指定 autoextend 屬性,并重啟服務(wù)器。

innodb_data_file_path=ibdata1:10M:autoextend

為避免系統(tǒng)表空間過大,可考慮使用獨(dú)立表空間或通用表空間存儲(chǔ)數(shù)據(jù)。

獨(dú)立表空間是默認(rèn)的表空間類型,在創(chuàng)建 InnoDB 表時(shí)會(huì)隱式使用。

獨(dú)立表空間(File-Per-Table Tablespaces)

獨(dú)立表空間,顧名思義,就是用戶創(chuàng)建的表空間,如果開啟獨(dú)立表空間參數(shù),那么一個(gè)表空間會(huì)對(duì)應(yīng)磁盤上的一個(gè)物理文件,每張表對(duì)應(yīng)一個(gè)文件,支持事務(wù)獨(dú)立管理。

其實(shí)表空間文件內(nèi)部還是組織為更復(fù)雜的邏輯結(jié)構(gòu),自頂向下可分為 segment(段)、extent(區(qū))和 page(頁)。

page 則是表空間數(shù)據(jù)存儲(chǔ)的基本單位,innodb 將表文件(xxx.ibd)按 page 切分,依類型不同,page 內(nèi)容也有所區(qū)別,最為常見的是存儲(chǔ)數(shù)據(jù)庫表的行記錄。

表空間下一級(jí)稱為 segment。segment 與數(shù)據(jù)庫中的索引相映射。

Innodb 引擎內(nèi),每個(gè)索引對(duì)應(yīng)兩個(gè) segment:管理葉子節(jié)點(diǎn)的 segment 和管理非葉子節(jié)點(diǎn) segment。

創(chuàng)建索引中很關(guān)鍵的步驟便是分配 segment,Innodb 內(nèi)部使用 INODE 來描述 segment。

segment 的下一級(jí)是 extent,extent 代表一組連續(xù)的 page,默認(rèn)為 64 個(gè) page,大小 1MB。

InnoDB 存儲(chǔ)引擎的邏輯存儲(chǔ)結(jié)構(gòu)大致如圖 2 所示。

圖 2圖 2

默認(rèn)情況下 InnoDB 存儲(chǔ)引擎有一個(gè)共享表空間 ibdata1,即所有數(shù)據(jù)都存放在這個(gè)表空間內(nèi)。

如果用戶啟用了參數(shù)innodb_file_per_table,則每張表內(nèi)的數(shù)據(jù)可以單獨(dú)放到一個(gè)表空間內(nèi)。

如果啟用了innodb_file_per_table的參數(shù),需要注意的是每張表的表空間內(nèi)存放的只是數(shù)據(jù)、索引和插入緩沖 Bitmap 頁.

其他類的數(shù)據(jù),如回滾(undo)信息,插入緩沖索引頁、系統(tǒng)事務(wù)信息,二次寫緩沖(Double write buffer)等還是存放在原來的系統(tǒng)表空間內(nèi)。

通用表空間(General Tablespaces)

通用表空間是一種共享的 InnoDB 表空間,通過 CREATE TABLESPACE 語法創(chuàng)建。

通用表空間提供以下功能:

  • 類似于系統(tǒng)表空間,通用表空間是一種共享表空間,能夠存儲(chǔ)多張表的數(shù)據(jù)。
  • 通用表空間在內(nèi)存占用上可能優(yōu)于獨(dú)立表空間。服務(wù)器會(huì)在表空間生命周期內(nèi)將表空間元數(shù)據(jù)保留在內(nèi)存中。相較于相同數(shù)量的表分散在多個(gè)獨(dú)立表空間中,更少的通用表空間內(nèi)存儲(chǔ)多張表能減少表空間元數(shù)據(jù)的內(nèi)存消耗。

通用表空間通過 CREATE TABLESPACE 語法創(chuàng)建。

CREATE TABLESPACE tablespace_name
    [ADD DATAFILE 'file_name']
    [FILE_BLOCK_SIZE = value]
        [ENGINE [=] engine_name]

通用表空間有什么不足?

通用表空間限制有以下限制:

  • 現(xiàn)有的表空間無法更改為通用表空間。
  • 不支持創(chuàng)建臨時(shí)通用表空間。
  • 通用表空間不支持臨時(shí)表。
  • 不支持將表分區(qū)放置在通用表空間中。
  • 在復(fù)制環(huán)境中,如果源和副本位于同一主機(jī)上,則不支持使用 ADD DATAFILE 子句,因?yàn)檫@會(huì)導(dǎo)致源和副本在同一位置創(chuàng)建同名的表空間,而這是不被支持的。

撤銷表空間(Undo Tablespaces)

MySQL InnoDB 引擎的 Undo Tablespaces(撤銷表空間)是磁盤架構(gòu)設(shè)計(jì)中用于管理事務(wù)回滾日志(Undo Log)的核心組件。

余彥瑭:InnoDB 引擎的 Undo Tablespaces(撤銷表空間)有啥用?

Undo 日志(Undo Log)主要用于事務(wù)異常時(shí)的數(shù)據(jù)回滾,在磁盤上 undo 日志保存在 Undo Tablespaces 中。

  1. 事務(wù)回滾與 MVCC 支持Undo 表空間存儲(chǔ)的 Undo Log 記錄了事務(wù)對(duì)數(shù)據(jù)的修改前鏡像,用于:

事務(wù)回滾時(shí)恢復(fù)數(shù)據(jù)原狀;

實(shí)現(xiàn)多版本并發(fā)控制(MVCC),支持非鎖定一致性讀。

  1. 分離系統(tǒng)表空間負(fù)載在 MySQL 5.7 之前,Undo Log 默認(rèn)存儲(chǔ)在系統(tǒng)表空間(ibdata1)中。隨著事務(wù)頻繁操作,ibdata1 文件會(huì)無限增長且無法自動(dòng)回收空間。5.7 及更高版本引入獨(dú)立 Undo 表空間,通過物理隔離減輕系統(tǒng)表空間壓力,提升性能。

MySQL 8.0 默認(rèn)創(chuàng)建 2 個(gè) Undo 表空間文件(undo_001 和 undo_002),每個(gè)初始大小為 16MB,通過參數(shù) innodb_undo_tablespaces 可調(diào)整數(shù)量(范圍 2-127),每個(gè)文件初始 16MB,支持自動(dòng)擴(kuò)展和截?cái)嗷厥铡?/p>

余彥瑭:“Undo 表空間的邏輯層級(jí)管理是咋樣的?”

回滾段(Rollback Segments):每個(gè) Undo 表空間包含 128 個(gè)回滾段(由 innodb_rollback_segments 控制),每個(gè)回滾段管理 1024 個(gè) Undo 段(Undo Segments)。

Undo 頁與日志記錄:Undo 段由多個(gè) 16KB 的頁組成,按事務(wù)類型分為 Insert Undo 段(僅用于回滾)和 Update Undo 段(用于 MVCC),前者事務(wù)提交后立即釋放,后者需等待無活躍讀視圖時(shí)清除。

通過多 Undo 表空間與回滾段的分區(qū)設(shè)計(jì),理論上支持高達(dá)數(shù)萬級(jí)并發(fā)事務(wù)(例如:128 表空間 × 128 回滾段 × 1024 Undo 段)。

如下圖所示。

圖片圖片

關(guān)鍵說明:

  • 每個(gè) Undo 表空間包含 128 個(gè)回滾段
  • 每個(gè)回滾段管理 1024 個(gè) Undo 段(按事務(wù)類型分類)
  • Undo 段由 16KB 頁 組成,存儲(chǔ)具體日志記錄

余彥瑭:說說 Undo Log 與 MVCC 的協(xié)作機(jī)制

Undo Log 與 MVCC 的協(xié)作機(jī)制如下圖所示:

圖片圖片

運(yùn)作原理:

  • 事務(wù)修改前將舊數(shù)據(jù)寫入 Undo Log
  • 讀事務(wù)通過 Read View 判斷可見性
  • 多版本數(shù)據(jù)通過 Undo Log 鏈回溯訪問

余彥瑭:“系統(tǒng)表空間與 Undo 表空間存儲(chǔ)有啥區(qū)別?”

特性

Undo 表空間

系統(tǒng)表空間(歷史方案)

存儲(chǔ)內(nèi)容

僅 Undo Log

數(shù)據(jù)字典、雙寫緩沖、Undo Log 等混合內(nèi)容

空間管理

支持自動(dòng)截?cái)?,避免文件膨?/p>

無法自動(dòng)回收,需手動(dòng)調(diào)整或重建

性能影響

減少 I/O 競(jìng)爭(zhēng),提升并發(fā)處理能力

高頻事務(wù)易導(dǎo)致文件過大,性能下降

版本支持

MySQL 5.7+ 默認(rèn)方案

MySQL 5.6 及更早版本

臨時(shí)表空間(Temporary Tablespaces)

InnoDB 臨時(shí)表空間分為 會(huì)話臨時(shí)表空間 和 全局臨時(shí)表空間,分別承擔(dān)不同角色:

  1. 會(huì)話臨時(shí)表空間(Session Temporary Tablespaces)
  • 用途:存儲(chǔ)用戶顯式創(chuàng)建的臨時(shí)表(CREATE TEMPORARY TABLE)以及優(yōu)化器生成的內(nèi)部臨時(shí)表(如排序、分組操作)。
  • 生命周期:會(huì)話斷開時(shí)自動(dòng)截?cái)嗖⑨尫呕爻?,文件擴(kuò)展名為 .ibt,默認(rèn)位于 #innodb_temp 目錄。
  • 分配機(jī)制:首次需要?jiǎng)?chuàng)建磁盤臨時(shí)表時(shí),從預(yù)分配的池中分配(默認(rèn)池包含 10 個(gè)表空間文件),每個(gè)會(huì)話最多分配 2 個(gè)表空間(用戶臨時(shí)表與優(yōu)化器內(nèi)部臨時(shí)表各一)。
  1. 全局臨時(shí)表空間(Global Temporary Tablespace):
  • 用途:存儲(chǔ)用戶臨時(shí)表的回滾段(Rollback Segments),支持事務(wù)回滾操作。
  • 文件配置:默認(rèn)文件名為 ibtmp1,初始大小 12MB,支持自動(dòng)擴(kuò)展,由參數(shù) innodb_temp_data_file_path 控制路徑與屬性。
  • 回收機(jī)制:服務(wù)器重啟時(shí)自動(dòng)刪除并重建,意外崩潰時(shí)需手動(dòng)清理。

Temporary Tablespaces 物理結(jié)構(gòu)

圖片圖片

圖示說明:

  • 全局臨時(shí)表空間:ibtmp1 存儲(chǔ)用戶臨時(shí)表的回滾段
  • 會(huì)話臨時(shí)表空間:#innodb_temp 目錄下預(yù)分配 10 個(gè) .ibt 文件池(默認(rèn)配置)
  • 每個(gè)會(huì)話最多激活 2 個(gè)臨時(shí)表空間(用戶臨時(shí)表 + 優(yōu)化器內(nèi)部臨時(shí)表)。

會(huì)話級(jí)臨時(shí)表空間生命周期

圖片圖片

關(guān)鍵點(diǎn):

  1. 首次需要磁盤臨時(shí)表時(shí)從池中分配
  2. 會(huì)話斷開連接后立即歸還空間
  3. 文件物理保留但內(nèi)容截?cái)啵愃苾?nèi)存池機(jī)制)

臨時(shí)表空間使用查詢流程

前面說過臨時(shí)表空間可存儲(chǔ)用戶顯式創(chuàng)建的臨時(shí)表(CREATE TEMPORARY TABLE)以及優(yōu)化器生成的內(nèi)部臨時(shí)表(如排序、分組操作)。

那它的查詢過程是怎樣的呢?

Tables(表)

余彥瑭:“在 MySQL 如何創(chuàng)建一張表?”

InnoDB 表通過 CREATE TABLE 語句創(chuàng)建;例如:

CREATE TABLE t1 (a INT, b CHAR (20), PRIMARY KEY (a)) ENGINE=InnoDB;

默認(rèn)情況下, InnoDB 表創(chuàng)建于每表獨(dú)立的表空間中。若要在 InnoDB 系統(tǒng)表空間中創(chuàng)建 InnoDB 表,需在創(chuàng)建表前禁用 innodb_file_per_table 變量。

比如,在數(shù)據(jù)庫 test 中創(chuàng)建一個(gè)表 show_index ,在 mysql 的 dataDirectory 目錄下就回出現(xiàn)一個(gè)名為 show_index.ibd 的數(shù)據(jù)文件。

在單個(gè)表的數(shù)據(jù)文件中,數(shù)據(jù)就是以多個(gè)頁的形式進(jìn)行排列。MySQL 默認(rèn)配置下,每 16K,即為一個(gè)頁。

InnoDB 表以 B+樹 組織數(shù)據(jù),每個(gè)表對(duì)應(yīng)一個(gè) 聚簇索引(Clustered Index),數(shù)據(jù)行的物理存儲(chǔ)順序與主鍵順序一致。

若未顯式定義主鍵,InnoDB 會(huì)隱式生成一個(gè) 6 字節(jié)的 Row ID 作為主鍵。

Row Formats 行格式

余彥瑭:表中的每一行數(shù)據(jù)是怎么存儲(chǔ)的?

表的 InnoDB 行格式?jīng)Q定了其行在磁盤上的物理存儲(chǔ)方式。

InnoDB 支持四種行格式,每種格式具有不同的存儲(chǔ)特性。

支持的行格式包括 REDUNDANT 、 COMPACT 、 DYNAMIC 和 COMPRESSED 。其中, DYNAMIC 行格式為默認(rèn)格式。


余彥瑭:它們有啥區(qū)別?

REDUNDANT 和 COMPACT 行格式支持的最大索引鍵前綴長度為 767 字節(jié),而 DYNAMIC 和 COMPRESSED 行格式則支持 3072 字節(jié)的索引鍵前綴長度。

在復(fù)制環(huán)境中,若源服務(wù)器上的 innodb_default_row_format 變量設(shè)置為 DYNAMIC ,而副本上設(shè)置為 COMPACT ,則以下未明確指定行格式的 DDL 語句在源服務(wù)器上執(zhí)行成功,但在副本上會(huì)失敗。

Primary Keys 主鍵

建議為創(chuàng)建的每個(gè)表定義一個(gè)主鍵。在選擇主鍵列時(shí),應(yīng)選擇具有以下特征的列:

  • 重要的查詢語句使用的列。
  • 列不能為空。
  • 從不包含重復(fù)值的列。
  • 一旦插入后極少甚至從不更改值的列。

例如,在包含人員信息的表中,你不會(huì)將主鍵設(shè)在 (firstname, lastname) 上,因?yàn)榭赡苡卸鄠€(gè)人員擁有相同的姓名,姓名列可能留空,且有時(shí)人們會(huì)更改姓名。

面對(duì)如此多的限制條件,通常沒有明顯的一組列適合作為主鍵,因此你會(huì)創(chuàng)建一個(gè)帶有數(shù)字 ID 的新列,作為主鍵。

最好的方式就是使用趨勢(shì)遞增的數(shù)字作為主鍵。

你也可以 在 InnoDB 表中使用 AUTO_INCREMENT 的列來定義主鍵自動(dòng)生成。

AUTO_INCREMENT 實(shí)現(xiàn)原理是什么?會(huì)鎖全表碼?

自增鎖模式通過 innodb_autoinc_lock_mode 變量在啟動(dòng)時(shí)配置。

自增主鍵鎖

“傳統(tǒng)”鎖模式

innodb_autoinc_lock_mode = 0 (“傳統(tǒng)”鎖模式),所有“INSERT 類”語句在向具有 AUTO_INCREMENT 列的表中插入時(shí)都會(huì)獲得一個(gè)特殊的表級(jí) AUTO-INC 鎖。

此鎖通常保持到語句的末尾(而不是事務(wù)的末尾),以確保在給定的 INSERT 語句序列中自動(dòng)增量值按可預(yù)測(cè)和可重復(fù)的順序分配,并確保任何給定語句分配的自動(dòng)增量值是連續(xù)的。

“連續(xù)”鎖模式

innodb_autoinc_lock_mode = 1 (“連續(xù)”鎖模式),“批量插入”使用特殊的 AUTO-INC 表級(jí)鎖,并保持到語句結(jié)束。這適用于所有 INSERT ... SELECT 、 REPLACE ... SELECT 和 LOAD DATA 語句。

這種鎖模式確保,在存在 INSERT 語句且行數(shù)未知(并且自增值在語句執(zhí)行過程中分配)的情況下,任何“ INSERT -類似”語句分配的所有自增值都是連續(xù)的,并且操作對(duì)基于語句的復(fù)制是安全的。

innodb_autoinc_lock_mode = 2 (“交錯(cuò)”鎖模式)

在這種鎖模式中,沒有“ INSERT -like”語句使用表級(jí) AUTO-INC 鎖,并且多個(gè)語句可以同時(shí)執(zhí)行。

這是最快且最可擴(kuò)展的鎖模式,但在使用基于語句的復(fù)制或從二進(jìn)制日志重放 SQL 語句的恢復(fù)場(chǎng)景時(shí)是不安全的。

在此鎖定模式下,自動(dòng)增量值在整個(gè)并發(fā)執(zhí)行的“ INSERT -like”語句中保證是唯一的且單調(diào)遞增。

然而,由于多個(gè)語句可以同時(shí)生成數(shù)字(即,數(shù)字的分配在語句之間交錯(cuò)進(jìn)行),任何給定語句插入的行生成的值可能不是連續(xù)的。

Indexes(索引)

InnoDB 的索引分為 聚簇索引 和 二級(jí)索引(Secondary Index),均采用 B+樹結(jié)構(gòu):

  • 聚簇索引:也稱 Clustered Index。是指關(guān)系表記錄的物理順序與索引的邏輯順序相同。由于一張表只能按照一種物理順序存放,一張表最多也只能存在一個(gè)聚集索引。葉子節(jié)點(diǎn)直接存儲(chǔ)行數(shù)據(jù)。
  • 二級(jí)索引:也叫 Secondary Index。指的是非葉子節(jié)點(diǎn)按照索引的鍵值順序存放,葉子節(jié)點(diǎn)存放索引鍵值以及對(duì)應(yīng)的主鍵鍵值。MySQL 里除了 INNODB 表主鍵外,其他的都是二級(jí)索引。葉子節(jié)點(diǎn)存儲(chǔ)主鍵值,需通過主鍵回表查詢數(shù)據(jù)。

下圖是一個(gè)聚集索引的 B+ Tree 圖。

圖片圖片

1 個(gè) B+ Tree Node,占據(jù)一個(gè)頁。

  • 在索引頁,頁的主要記錄部分(User Records)存放的Record = record header + index key + page pointer。
  • 在數(shù)據(jù)頁,則是按表創(chuàng)建時(shí)的row_format類型存放完整數(shù)據(jù)行記錄。 row_format 類型分別有:Compact、Redundant、Compressed和Dynamic。

因此,在聚集索引中,非葉子節(jié)點(diǎn)都為索引頁,葉子節(jié)點(diǎn)為數(shù)據(jù)頁;

在輔助索引中,非葉子節(jié)點(diǎn)和葉子節(jié)點(diǎn)都為索引頁。不同的是,葉子節(jié)點(diǎn)里記錄的是聚集索引中的主鍵 ID 值。

INNODB 表的二級(jí)索引,如下圖所示,圖片來自「一樹一溪」:

注意,在索引頁的 Record 中的page pointer,指向的是頁,而非具體的記錄行。

并且 Record 的index key,為指向的 page records 的起始鍵值。

如果主鍵較長,二級(jí)索引會(huì)占用更多空間,因此擁有較短的主鍵是有利的。

在表空間文件的一個(gè)頁的結(jié)構(gòu)上,內(nèi)容布局為:

圖片圖片

在聚集索引中,數(shù)據(jù)頁內(nèi)除了按照主鍵大小進(jìn)行記錄存放以外,在File header中,有兩個(gè)字段:fil_page_prev 和fil_page_next, 分別記錄了上一頁/下一頁的偏移量(offset),用以實(shí)現(xiàn)數(shù)據(jù)頁在 B+ Tree 葉子位置的雙向鏈表結(jié)構(gòu)。

數(shù)據(jù)如何被查找檢索呢?

通過 B+ Tree 結(jié)構(gòu),可以明顯看到,通過 B+ Tree 查找,可以定位到索引最后指向的數(shù)據(jù)頁,并不能找到具體的記錄本身。

這時(shí),數(shù)據(jù)庫會(huì)將該頁加載到內(nèi)存中,然后通過Page Directory進(jìn)行二分查找。

余彥瑭:“索引使用單調(diào)遞增和 UUID 有什么區(qū)別嗎?”

這個(gè)問題問的好,我們一定要杜絕使用 UUID 生成的數(shù)據(jù)作為索引。

順序主鍵(如自增 ID)插入時(shí),數(shù)據(jù)頁填充率高,減少頁分裂。我們根據(jù)上文知道,索引是有序排列的一個(gè) B+tree,單調(diào)遞增天然有序,這樣才能高效的使用索引查詢數(shù)據(jù)。

什么是覆蓋索引優(yōu)化?

-- 示例表結(jié)構(gòu)
CREATETABLEusers (
idINT PRIMARY KEY,
nameVARCHAR(50),
  age INT,
INDEX idx_name_age (name, age)
);

-- 覆蓋索引查詢
SELECTid, name, age FROMusersWHEREname = 'Alice';
  • 原理:查詢字段全部包含在二級(jí)索引中時(shí),無需回表。
  • 執(zhí)行計(jì)劃:Extra 列顯示 Using index。

余彥瑭:“排序索引(Sorted Indexes)是什么?”

B+ 樹有序性:所有索引(聚簇/二級(jí))均按索引鍵值排序存儲(chǔ),支持高效范圍查詢和排序操作。

頁內(nèi)排序:?jiǎn)蝹€(gè)數(shù)據(jù)頁內(nèi)的記錄按主鍵順序存儲(chǔ),頁之間通過雙向鏈表連接。

所以我們么可以使用索引看來優(yōu)化排序查詢。

-- 利用索引排序
SELECT * FROM users ORDER BY id DESC LIMIT 10;

避免 Filesort:若 ORDER BY 子句與索引順序一致,執(zhí)行計(jì)劃顯示 Using index。

Doublewrite Buffer (雙寫緩沖)

InnoDB 是 MySQL 中一種常用的事務(wù)性存儲(chǔ)引擎,它具有很多優(yōu)秀的特性。

其中,Doublewrite Buffer 是 InnoDB 的一個(gè)重要特性之一。

為什么需要 Doublewrite Buffer

InnoDB 頁大小為 16KB,而操作系統(tǒng)(如 Linux)頁大小為 4KB,單次寫入需拆分 4 個(gè) OS 頁。

可以使用如下命令查看 MySQL 的 Page 大?。?/p>

SHOW VARIABLES LIKE 'innodb_page_size';

而 MySQL 程序是跑在 Linux 操作系統(tǒng)上的,MySQL 中一頁數(shù)據(jù)刷到磁盤,要寫 4 個(gè)文件系統(tǒng)里的頁。

圖片圖片

需要注意的是,這個(gè)操作并非原子操作,比如我操作系統(tǒng)寫到第二個(gè)頁的時(shí)候,Linux 機(jī)器斷電了,這時(shí)候就會(huì)出現(xiàn)問題了。

造成”頁數(shù)據(jù)損壞“。并且這種”頁數(shù)據(jù)損壞“靠 redo 日志是無法修復(fù)的。

Redo log 中記錄的是對(duì)頁的物理操作,而不是頁面的全量記錄,而如果發(fā)生 partial page write(部分頁寫入)問題時(shí),出現(xiàn)問題的是未修改過的數(shù)據(jù),此時(shí)重做日志(Redo Log)無能為力。

Doublewrite Buffer 的出現(xiàn)就是為了解決上面的這種情況,雖然名字帶了 Buffer,但實(shí)際上 Doublewrite Buffer 是內(nèi)存+磁盤的結(jié)構(gòu)。

Doublewrite Buffer 是一種特殊文件 flush 技術(shù),帶給 InnoDB 存儲(chǔ)引擎的是數(shù)據(jù)頁的可靠性。

它的作用是,在把頁寫到磁盤數(shù)據(jù)文件之前,InnoDB 先把它們寫到一個(gè)叫 doublewrite buffer(雙寫緩沖區(qū))的共享表空間內(nèi),在寫 doublewrite buffer 完成后,InnoDB 才會(huì)把頁寫到數(shù)據(jù)文件的適當(dāng)?shù)奈恢谩?/p>

如果在寫頁的過程中發(fā)生意外崩潰,InnoDB 在稍后的恢復(fù)過程中在 doublewrite buffer 中找到完好的 page 副本用于恢復(fù)。

架構(gòu)設(shè)計(jì)

Doublewrite Buffer 采用 內(nèi)存+磁盤雙層結(jié)構(gòu),關(guān)鍵組件如下:

內(nèi)存結(jié)構(gòu)

  • 容量固定為 128 個(gè)頁(2MB),每個(gè)頁 16KB。
  • 數(shù)據(jù)頁刷盤前,通過 memcpy 拷貝至內(nèi)存 Doublewrite Buffer。

磁盤結(jié)構(gòu)

  • 位于系統(tǒng)表空間(ibdata),分為 2 個(gè)區(qū)(extent1/extent2),共 2MB。
  • 數(shù)據(jù)以 順序?qū)?nbsp;方式寫入,避免隨機(jī) I/O 開銷。

工作流程如下圖所示:

圖片圖片

如上圖所示,當(dāng)有數(shù)據(jù)修改且頁數(shù)據(jù)要刷盤時(shí):

  1. 第一步:記錄 Redo log。
  2. 第二步:臟頁從 Buffer Pool 拷貝至內(nèi)存中的 Doublewrite Buffer。
  3. 第三步:Doublewrite Buffer 的內(nèi)存里的數(shù)據(jù)頁,會(huì) fsync 刷到 Doublewrite Buffer 的磁盤上,分兩次寫入磁盤共享表空間中(連續(xù)存儲(chǔ),順序?qū)?,性能很?,每次寫 1MB;
  4. 第四步:Doublewrite Buffer 的內(nèi)存里的數(shù)據(jù)頁,再刷到數(shù)據(jù)磁盤存儲(chǔ) .ibd 文件上(離散寫);

時(shí)序圖如下:

圖片圖片

崩潰恢復(fù)

如果第三步前,發(fā)生了崩潰,可以通過第一步記錄的 Redo log 來恢復(fù)。

如果第三步完成后發(fā)生了崩潰, InnoDB 存儲(chǔ)引擎可以從共享表空間中的 Double write 中找到該頁的一個(gè)副本,將其復(fù)制到獨(dú)立表空間文件,再應(yīng)用 Redo log 恢復(fù)。

在正常的情況下,MySQL 寫數(shù)據(jù)頁時(shí),會(huì)寫兩遍到磁盤上,第一遍是寫到 doublewrite buffer,第二遍是寫到真正的數(shù)據(jù)文件中,這就是“Doublewrite”的由來。

Doublewrite Buffer 通過 兩次寫 機(jī)制,在內(nèi)存和磁盤間構(gòu)建冗余副本,成為 InnoDB 保障數(shù)據(jù)完整性的基石。

其架構(gòu)設(shè)計(jì)平衡了性能與可靠性,尤其在高并發(fā)或異常宕機(jī)場(chǎng)景下表現(xiàn)突出。

Redo Log (重做日志)

重新回顧下 MySQL InnoDB 的內(nèi)存和磁盤架構(gòu)設(shè)計(jì)圖。

我們的目光是關(guān)注點(diǎn)在于左側(cè)內(nèi)存架構(gòu)的 Log Buffer 以及右側(cè)磁盤架構(gòu)的 Redo Log 文件。

圖 1圖 1

余彥瑭:Redo Log 有啥用呢?

姐姐你可知道,在數(shù)據(jù)庫系統(tǒng)中,持久性(Durability) 是事務(wù) ACID 特性的核心要求之一。

其核心問題是:如何確保提交的事務(wù)在崩潰后不丟失? 直接修改磁盤數(shù)據(jù)頁的隨機(jī) I/O 性能低下,且無法保證崩潰瞬間數(shù)據(jù)的完整性。

InnoDB 的解決方案是引入 Redo Log(重做日志),通過順序?qū)懭罩?+ 內(nèi)存緩沖的組合設(shè)計(jì)實(shí)現(xiàn)高性能的持久化保障。


余彥瑭:“說說看 Redo Log 如何保證已提交的事務(wù)不丟失”

當(dāng)數(shù)據(jù)庫意外崩潰時(shí),如何保證已提交事務(wù)不丟失?InnoDB 通過 WAL(Write-Ahead Logging)機(jī)制 解決這一核心問題,其實(shí)現(xiàn)依賴兩大核心組件:

  1. Log Buffer:內(nèi)存中的日志緩沖區(qū)
  2. Redo Log:磁盤上的順序?qū)懭罩疚募ㄟ^二者的協(xié)同,InnoDB 在保證 ACID 持久性的同時(shí),將隨機(jī)寫轉(zhuǎn)換為順序?qū)懀瑢?shí)現(xiàn)性能與可靠性的完美平衡。

圖片圖片

Log Buffer 是一個(gè)內(nèi)存層的環(huán)形緩沖區(qū)。

圖片圖片

關(guān)鍵字段說明:

  • buf:指向環(huán)形緩沖區(qū)的內(nèi)存地址
  • write_lsn:原子變量,實(shí)現(xiàn)多線程無鎖寫
  • hdr_no:塊序號(hào)(用于崩潰恢復(fù)時(shí)定位日志位置)

余彥瑭:“李老師,當(dāng)事務(wù)生成 Redo 記錄后,關(guān)鍵步驟有哪些?”

當(dāng)事務(wù)生成 Redo Record 后:

/* 源碼路徑:storage/innobase/log/log0buf.cc */
void log_buffer_write(log_t& log, byte* record, size_t len) {
    // 1. 獲取互斥鎖(短時(shí)鎖)
    mutex_enter(&log.mutex);

    // 2. 分配連續(xù)空間(跨塊處理)
    lsn_t start_lsn = log.assign_lsn(len);

    // 3. 復(fù)制日志到緩沖區(qū)
    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 機(jī)制全流程如下圖所示:

圖片圖片

Undo Logs (撤銷日志)

MySQL InnoDB 引擎的事務(wù)隔離性由鎖來實(shí)現(xiàn)。原子性、一致性、持久性通過數(shù)據(jù)庫的 redo log 和 undo log 來完成。

余彥瑭:“Undo Log 的本質(zhì)作用是什么?”

Undo Log 是 InnoDB 實(shí)現(xiàn)事務(wù) 原子性(Atomicity) 和 多版本并發(fā)控制(MVCC) 的核心組件,主要解決兩大關(guān)鍵問題:

  1. 事務(wù)回滾:允許事務(wù)失敗時(shí)恢復(fù)到修改前的狀態(tài)(原子性)
  2. 讀一致性:提供非鎖定讀取(Non-Locking Read)的歷史版本(MVCC)

與 Redo Log 形成鮮明對(duì)比:

特性

Redo Log

Undo Log

目的

保證持久性

保證原子性和隔離性

寫入方向

順序?qū)?/p>

隨機(jī)寫(回滾段中)

存儲(chǔ)內(nèi)容

物理日志(頁修改)

邏輯日志(行修改前的值)

生命周期

事務(wù)提交后保留到檢查點(diǎn)

事務(wù)提交后保留到無讀視圖引用

清理機(jī)制

Checkpoint 截?cái)?/p>

Purge 線程異步清理

當(dāng)事務(wù)修改數(shù)據(jù)時(shí), Undo Log 如何生成呢?

關(guān)鍵代碼邏輯(row_upd_rec_in_place函數(shù)):

/* 存儲(chǔ)位置:storage/innobase/row/row0upd.cc */
void row_upd_rec_in_place(...) {
    // 1. 創(chuàng)建 Undo Log Record
    trx_undo_report_row_operation(...);

    // 2. 寫入回滾段
    trx_undo_report_update_impl(...);

    // 3. 設(shè)置行回滾指針
    row_upd_rec_set_roll_ptr(...);
}

時(shí)序圖如下所示:

圖片圖片

余彥瑭:過期 Undo Log 該如何處理呢?

姐姐問得好,Purge 線程負(fù)責(zé)清理已提交事務(wù)的過期 Undo Log。

就這樣,InnoDB 通過三大日志機(jī)制構(gòu)建完整事務(wù)系統(tǒng):

圖片圖片

設(shè)計(jì)哲學(xué)啟示:

  1. 分層解耦
  • Redo 處理物理持久化
  • Undo 處理邏輯回滾
  • Binlog 處理邏輯復(fù)制
  1. 空間換時(shí)間Undo 保留歷史版本換取無鎖讀能力
  2. 延遲處理藝術(shù)Purge 機(jī)制避免事務(wù)提交時(shí)的同步清理開銷

好了,今天就到這。

責(zé)任編輯:武曉燕 來源: 碼哥跳動(dòng)
相關(guān)推薦

2025-01-15 13:19:09

MySQL日志事務(wù)

2024-06-11 00:00:02

MySQL數(shù)據(jù)庫系統(tǒng)

2021-01-26 13:47:08

MySQL存儲(chǔ)數(shù)據(jù)

2024-05-28 00:10:00

JavaMySQL數(shù)據(jù)庫

2020-08-20 12:10:42

MySQL日志數(shù)據(jù)庫

2024-05-30 08:03:17

2023-11-23 13:17:39

MySQL?數(shù)據(jù)庫

2025-06-06 07:02:43

2024-12-16 00:00:05

MySQL二進(jìn)制數(shù)據(jù)

2025-01-20 08:20:00

redo logMySQL數(shù)據(jù)庫

2025-10-09 02:22:00

MySQLMVCC庫存數(shù)量

2021-07-28 08:32:03

MySQLRedo存儲(chǔ)

2021-10-04 09:23:30

Redo日志內(nèi)存

2021-05-28 11:18:50

MySQLbin logredo log

2022-08-26 10:11:26

MySQL數(shù)據(jù)庫

2024-08-30 10:29:21

2024-12-31 00:00:01

驅(qū)動(dòng)設(shè)計(jì)應(yīng)用場(chǎng)景業(yè)務(wù)邏輯

2020-09-21 10:50:24

Java多線程代碼

2010-01-06 09:30:51

Oracle Redo

2025-04-23 08:31:26

Java并發(fā)框架
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)