一條 INSERT 背后的秘密:揭開 InnoDB 記錄結(jié)構(gòu)的神秘面紗
你以為你寫了一條 SQL,其實你是在和數(shù)據(jù)庫的一整套存儲機制打交道。
一、引言:懂記錄結(jié)構(gòu),真的很重要!
開篇三連擊:
- 當(dāng)你敲下INSERT時,數(shù)據(jù)是在磁盤「蓋房子」還是「搭積木」?
- 行格式里藏著哪些「加密代碼」?
- 一條“莫名其妙”的慢查詢,根源可能是行格式選錯?
這些問題的答案,就藏在 InnoDB 的記錄結(jié)構(gòu)里。
我們在寫 SQL 的時候,經(jīng)常只關(guān)注“寫對了沒”、“跑起來沒報錯”。但真正理解 MySQL 的底層行為,往往要從一句簡單的 INSERT 開始。
二、從一條INSERT語句開始說起
當(dāng)我們執(zhí)行INSERT INTO users (name, age,address) VALUES ('張三', 25,'北京.海淀');這條語句時,數(shù)據(jù)并不會直接 “一股腦” 地塞進(jìn)磁盤。InnoDB 會按照特定的規(guī)則,將數(shù)據(jù) “搭建” 成特定的結(jié)構(gòu),然后再存儲到磁盤上。
為了更好地理解這個過程,我們先來對比一下行數(shù)據(jù)以及行結(jié)構(gòu)。
假設(shè)我們有一張users表,包含id、name、age、address四個字段。
CREATE TABLEusers (
idINTUNSIGNEDNOTNULL AUTO_INCREMENT COMMENT'主鍵ID',
nameVARCHAR(100) NOTNULLCOMMENT'用戶姓名',
age INTUNSIGNEDCOMMENT'年齡',
address VARCHAR(255) COMMENT'地址',
PRIMARY KEY (id)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='用戶信息表';
當(dāng)我們插入一條數(shù)據(jù)(1, '張三', 25,'北京.海淀')時,從表面上看,我們看到的行數(shù)據(jù)是這樣的:
id | name | age | address |
1 | 張三 | 25 | 北京海淀 |
然而在 InnoDB 中,一條數(shù)據(jù)并非簡單存儲,而是拆分成多個部分:記錄頭信息保存元數(shù)據(jù),變長字段列表記錄變長字段及長度,NULL 值列表標(biāo)記哪些字段為 NULL。
InnoDB目前支持四種行格式:
- COMPACT(最常用)
- REDUNDANT(MySQL 5.0之前)
- DYNAMIC(MySQL 5.7默認(rèn))
- COMPRESSED(壓縮格式)
COMPACT 行格式是最常用的“標(biāo)準(zhǔn)模板”,掌握它能幫助你理解 InnoDB 記錄結(jié)構(gòu)的核心。
三、COMPACT 行格式詳細(xì)介紹:數(shù)據(jù)存儲的 “標(biāo)準(zhǔn)模板”
廢話不多說,直接看圖:
1. 記錄頭信息:數(shù)據(jù)的 “身份證”
記錄頭信息僅占5字節(jié),卻包含記錄類型、刪除標(biāo)記、B+樹位置等關(guān)鍵信息,是記錄的重要標(biāo)識。
當(dāng)我們執(zhí)行DELETE語句刪除一條記錄時,InnoDB 并不會立即從磁盤上刪除這條記錄,而是在記錄頭信息中設(shè)置刪除標(biāo)記,后續(xù)再通過專門的機制進(jìn)行清理。
字段 (Field) | 位數(shù) (Bits) | 描述 (Description) |
預(yù)留位1 | 1 | 保留位,目前沒有用到。 |
預(yù)留位2 | 1 | 保留位,目前沒有用到。 |
delete_mask | 1 | 標(biāo)記該記錄是否被刪除,1-是,0-否 |
min_rec_mask | 1 | 標(biāo)記該記錄是否是B+樹葉子節(jié)點中最小的記錄,1-是,0-否 |
record_type | 3 | 標(biāo)記記錄的類型。000表示普通記錄,001表示最小值記錄,010表示目錄記錄,011表示最大值記錄。 |
n_owned | 4 | 表示當(dāng)前記錄擁有的記錄數(shù) |
heap_no | 13 | 標(biāo)記當(dāng)前記錄在當(dāng)前頁面(Page)中的相對位置(槽號)。 |
next_record | 16 | 表示下一條記錄的相對位置 |
預(yù)留位2 | 1 | 保留位,目前沒有用到。 |
2. 變長字段列表:應(yīng)對 “變化多端” 的數(shù)據(jù)
在實際應(yīng)用中,很多字段的數(shù)據(jù)長度是不固定的,比如VARCHAR、TEXT、BLOB等類型的字段。變長字段列表就是為了應(yīng)對這些 “變化多端” 的數(shù)據(jù)而設(shè)計的。它會記錄哪些字段是變長的,以及它們的長度。
變長字段列表采用倒排的方式存儲,也就是說,它從右往左存儲每個變長字段的長度。
(1) 變長字段列表如何存儲實際數(shù)據(jù)?
當(dāng)執(zhí)行插入語句INSERT INTO users VALUES(1, '張三', 25, '北京.海淀');時,我們來分析一下變長字段列表的存儲方式。
① 字段分析
- id:INT 類型,固定長度 4 字節(jié),不屬于變長字段
- name:VARCHAR (100),實際存儲 ' 張三 ',UTF-8 編碼下每個漢字占 3 字節(jié),共 6 字節(jié)
- age:INT 類型,固定長度 4 字節(jié),不屬于變長字段
- address:VARCHAR (255),實際存儲 ' 北京.海淀 ',共包含 5 個字符(2 個漢字、1 個點、2 個漢字),每個漢字 3 字節(jié),點 1 字節(jié),共 13 字節(jié)
② 變長字段列表的倒排存儲
上面的例子中,有兩個變長字段:name和address。它們的長度分別是 6 字節(jié)和 13 字節(jié)。根據(jù)倒排存儲規(guī)則,變長字段列表會按照從右到左的順序記錄這些長度。
- 表定義順序是:name, address
- 倒排后,存的時候順序是:address, name
因此,變長字段列表的內(nèi)容為:[13,6]。
這些值并不是直接以十進(jìn)制存入,而是編碼成 1~2 字節(jié)的二進(jìn)制形式(依字段長度大小決定)。
3. NULL 值列表:節(jié)省空間的 “小能手”
在 InnoDB 中,NULL 值列表是一種節(jié)省空間的巧妙設(shè)計。它不存儲 NULL 的實際值,而是用每個字段對應(yīng)的一位二進(jìn)制位來標(biāo)記:
- 1 表示該字段為 NULL;
- 0 表示不為 NULL。
在計算 InnoDB 記錄結(jié)構(gòu)中的 NULL 值列表時,只有那些“允許為 NULL”的字段才會被納入統(tǒng)計。
以 users 表為例:
- id 是主鍵,不能為 NULL;
- name 被 NOT NULL 明確聲明,也不能為 NULL;
- age 和 address 沒有限定 NOT NULL,默認(rèn)是可以為 NULL 的。
所以,NULL 值列表中只包含 age 和 address 這兩個字段的狀態(tài)位。
NULL 值列表的位順序,是按照表結(jié)構(gòu)中允許 NULL 字段的出現(xiàn)順序排列的,且僅包含這些字段。
四、行溢出:當(dāng)數(shù)據(jù)太大時會發(fā)生什么?
1. 為什么會出現(xiàn)行溢出?
InnoDB 的數(shù)據(jù)存儲以 “頁” 為基本單位,每頁默認(rèn)大小為 16KB。當(dāng)我們插入的數(shù)據(jù)(如一篇幾萬字的文章、高清圖片的二進(jìn)制數(shù)據(jù))長度超過一頁能容納的空間時,InnoDB 就會遇到 “空間不夠用” 的難題。就像你想把 100 本書塞進(jìn)只能裝 50 本書的箱子,自然裝不下。
2. 什么是行溢出?
為了解決上述問題,InnoDB 引入了行溢出機制:
- 當(dāng)數(shù)據(jù)過長時,它會把超出數(shù)據(jù)頁容量的部分 “搬” 到額外的溢出頁中存儲;
- 并在原數(shù)據(jù)頁保留一個指向溢出頁的指針(通常是20字節(jié))。
這就好比把裝不下的書先放在旁邊的臨時箱子,再在原本的箱子貼上標(biāo)簽注明 “其余書在隔壁箱”。
行溢出雖解決大字段存儲,但帶來性能隱患,如查詢慢、空間管理復(fù)雜、碎片增多。優(yōu)化可從多方面入手:拆大字段表、選適配數(shù)據(jù)類型與行格式,控制字段長度,同時避免在大字段建索引,以此提升數(shù)據(jù)庫性能。
五、結(jié)語:深入底層,才能掌控全局
數(shù)據(jù)庫的 “高性能密碼” 藏在底層結(jié)構(gòu)里。從 INSERT 語句到磁盤存儲,COMPACT 行格式的字段管理、行溢出的優(yōu)化邏輯,都是提升數(shù)據(jù)庫能力的關(guān)鍵。懂記錄結(jié)構(gòu),才能在面試中從容應(yīng)答,在優(yōu)化時直擊痛點。
記住:深挖底層原理,才能讓技術(shù)成長 “知其所以然”。歡迎留言交流,一起解鎖更多數(shù)據(jù)庫奧秘!