資料表的列格式決定其列在實體上如何儲存,這反過來會影響查詢和 DML 操作的效能。由於更多列可放入單一磁碟頁面,因此查詢和索引查閱可以更快地運作,緩衝池中需要較少的快取記憶體,並且寫出更新的值所需的 I/O 也較少。
每個資料表中的資料會分成頁面。構成每個資料表的頁面會以稱為 B 樹索引的樹狀資料結構排列。資料表資料和次要索引都使用這種結構。代表整個資料表的 B 樹索引稱為叢集索引,其會根據主鍵欄來組織。叢集索引資料結構的節點包含列中所有欄的值。次要索引結構的節點包含索引欄和主鍵欄的值。
可變長度欄是欄值儲存在 B 樹索引節點中的規則例外。對於太長而無法放入 B 樹頁面的可變長度欄,會儲存在稱為溢位頁面的個別配置磁碟頁面上。這類欄稱為頁外欄。頁外欄的值會儲存在溢位頁面的單一連結清單中,每個這類欄都有其自己的單一或多個溢位頁面清單。視欄長度而定,所有或部分可變長度欄的值會儲存在 B 樹中,以避免浪費儲存空間並必須讀取個別的頁面。
InnoDB
儲存引擎支援四種列格式:REDUNDANT
、COMPACT
、DYNAMIC
和 COMPRESSED
。
表格 17.14 InnoDB 列格式概述
列格式 | 精簡儲存特性 | 增強的可變長度欄儲存 | 大型索引鍵前置詞支援 | 壓縮支援 | 支援的資料表空間類型 |
---|---|---|---|---|---|
REDUNDANT |
否 | 否 | 否 | 否 | 系統、每個資料表、一般 |
COMPACT |
是 | 否 | 否 | 否 | 系統、每個資料表、一般 |
DYNAMIC |
是 | 是 | 是 | 否 | 系統、每個資料表、一般 |
COMPRESSED |
是 | 是 | 是 | 是 | 每個資料表、一般 |
以下主題描述列格式儲存特性,以及如何定義和判斷資料表的列格式。
REDUNDANT
格式提供與較舊 MySQL 版本的相容性。
使用 REDUNDANT
列格式的表格,會在 B 樹節點內的索引記錄中儲存可變長度欄位值(VARCHAR
、VARBINARY
,以及 BLOB
和 TEXT
類型)的前 768 個位元組,其餘部分則儲存在溢出頁面上。大於或等於 768 個位元組的固定長度欄位會被編碼為可變長度欄位,可以儲存在頁面外。例如,如果字元集的最大位元組長度大於 3,則 CHAR(255)
欄位可能會超過 768 個位元組,如同 utf8mb4
一樣。
如果欄位的值為 768 個位元組或更少,則不會使用溢出頁面,並且由於值完全儲存在 B 樹節點中,可能會節省一些 I/O。這對於相對較短的 BLOB
欄位值效果良好,但可能會導致 B 樹節點充滿資料而不是索引鍵值,從而降低其效率。具有許多 BLOB
欄位的表格可能會導致 B 樹節點變得太滿,且包含的列太少,使得整個索引的效率不如列較短或欄位值儲存在頁面外的情況。
REDUNDANT 列格式儲存特性
REDUNDANT
列格式具有以下儲存特性
每個索引記錄都包含一個 6 位元組的標頭。標頭用於連結連續記錄,以及用於列級鎖定。
叢集索引中的記錄包含所有使用者定義欄位的欄位。此外,還有一個 6 位元組的交易 ID 欄位和一個 7 位元組的回滾指標欄位。
如果沒有為表格定義主鍵,則每個叢集索引記錄還包含一個 6 位元組的列 ID 欄位。
每個次要索引記錄都包含為叢集索引鍵定義的所有主鍵欄位,這些欄位不在次要索引中。
記錄包含指向記錄每個欄位的指標。如果記錄中欄位的總長度小於 128 個位元組,則指標為一個位元組;否則,為兩個位元組。指標陣列稱為記錄目錄。指標指向的區域是記錄的資料部分。
大於或等於 768 個位元組的固定長度欄位會被編碼為可變長度欄位,可以儲存在頁面外。例如,如果字元集的最大位元組長度大於 3,則
CHAR(255)
欄位可能會超過 768 個位元組,如同utf8mb4
一樣。SQL
NULL
值在記錄目錄中保留一或兩個位元組。如果儲存在可變長度欄位中,則 SQLNULL
值在記錄的資料部分中保留零個位元組。對於固定長度欄位,則在記錄的資料部分中保留欄位的固定長度。NULL
值保留固定空間允許欄位從NULL
就地更新為非NULL
值,而不會導致索引頁面碎片。
與 REDUNDANT
列格式相比,COMPACT
列格式將列儲存空間減少約 20%,代價是增加某些操作的 CPU 使用率。如果您的工作負載是受限於快取命中率和磁碟速度的典型工作負載,則 COMPACT
格式可能會更快。如果工作負載受限於 CPU 速度,則精簡格式可能會較慢。
使用 COMPACT
列格式的表格,會在 B 樹節點內的索引記錄中儲存可變長度欄位值(VARCHAR
、VARBINARY
,以及 BLOB
和 TEXT
類型)的前 768 個位元組,其餘部分則儲存在溢出頁面上。大於或等於 768 個位元組的固定長度欄位會被編碼為可變長度欄位,可以儲存在頁面外。例如,如果字元集的最大位元組長度大於 3,則 CHAR(255)
欄位可能會超過 768 個位元組,如同 utf8mb4
一樣。
如果欄位的值為 768 個位元組或更少,則不會使用溢出頁面,並且由於值完全儲存在 B 樹節點中,可能會節省一些 I/O。這對於相對較短的 BLOB
欄位值效果良好,但可能會導致 B 樹節點充滿資料而不是索引鍵值,從而降低其效率。具有許多 BLOB
欄位的表格可能會導致 B 樹節點變得太滿,且包含的列太少,使得整個索引的效率不如列較短或欄位值儲存在頁面外的情況。
COMPACT 列格式儲存特性
COMPACT
列格式具有以下儲存特性
每個索引記錄都包含一個 5 位元組的標頭,該標頭前面可能有一個可變長度的標頭。標頭用於連結連續記錄,以及用於列級鎖定。
記錄標頭的可變長度部分包含一個位元向量,用於指示
NULL
欄位。如果索引中可以為NULL
的欄位數量為N
,則位元向量佔用CEILING(
個位元組。(例如,如果存在 9 到 16 個可以為N
/8)NULL
的欄位,則位元向量使用兩個位元組。)除了這個向量中的位元之外,為NULL
的欄位不佔用空間。標頭的可變長度部分還包含可變長度欄位的長度。每個長度佔用一或兩個位元組,具體取決於欄位的最大長度。如果索引中的所有欄位都為NOT NULL
且具有固定長度,則記錄標頭沒有可變長度部分。對於每個非
NULL
的可變長度欄位,記錄標頭包含欄位的長度,長度佔用一或兩個位元組。只有當部分欄位外部儲存在溢出頁面中,或者最大長度超過 255 個位元組且實際長度超過 127 個位元組時,才需要兩個位元組。對於外部儲存的欄位,2 位元組的長度表示內部儲存部分的長度加上指向外部儲存部分的 20 位元組指標。內部部分為 768 個位元組,因此長度為 768+20。20 位元組的指標儲存欄位的真實長度。記錄標頭後面接著非
NULL
欄位的資料內容。叢集索引中的記錄包含所有使用者定義欄位的欄位。此外,還有一個 6 位元組的交易 ID 欄位和一個 7 位元組的回滾指標欄位。
如果沒有為表格定義主鍵,則每個叢集索引記錄還包含一個 6 位元組的列 ID 欄位。
每個次要索引記錄都包含為叢集索引鍵定義的所有主鍵欄位,這些欄位不在次要索引中。如果任何主鍵欄位是可變長度,則每個次要索引的記錄標頭都有一個可變長度部分來記錄它們的長度,即使次要索引是在固定長度欄位上定義的也是如此。
在內部,對於非可變長度的字元集,固定長度的字元欄位(例如
CHAR(10)
)以固定長度格式儲存。VARCHAR
欄位不會截斷尾隨空格。在內部,對於可變長度的字元集(例如
utf8mb3
和utf8mb4
),InnoDB
嘗試透過修剪尾隨空格,以N
個位元組儲存CHAR(
。如果N
)CHAR(
欄位值的位元組長度超過N
)N
個位元組,則將尾隨空格修剪為最大欄位值位元組長度。CHAR(
欄位的最大長度為最大字元位元組長度 ×N
)N
。至少保留
N
個位元組用於CHAR(
。在許多情況下,保留最小空間N
)N
可讓欄位更新就地完成,而不會導致索引頁面碎片。相比之下,使用REDUNDANT
列格式時,CHAR(
欄位會佔用最大字元位元組長度 ×N
)N
。大於或等於 768 個位元組的固定長度欄位會被編碼為可變長度欄位,可以儲存在頁面外。例如,如果字元集的最大位元組長度大於 3,則
CHAR(255)
欄位可能會超過 768 個位元組,如同utf8mb4
一樣。
DYNAMIC
列格式提供的儲存特性與 COMPACT
列格式相同,但增加了對長可變長度欄位的增強儲存能力,並支援大型索引鍵前綴。
當使用 ROW_FORMAT=DYNAMIC
建立表格時,InnoDB
可以將長可變長度欄位值(對於 VARCHAR
、VARBINARY
,以及 BLOB
和 TEXT
類型)完全儲存在頁面外,叢集索引記錄僅包含指向溢出頁面的 20 位元組指標。大於或等於 768 個位元組的固定長度欄位會被編碼為可變長度欄位。例如,如果字元集的最大位元組長度大於 3,則 CHAR(255)
欄位可能會超過 768 個位元組,如同 utf8mb4
一樣。
欄位是否儲存在頁面外取決於頁面大小和列的總大小。當列太長時,會選擇最長的欄位進行頁面外儲存,直到叢集索引記錄適合 B 樹頁面。TEXT
和 BLOB
欄位小於或等於 40 個位元組時,會內嵌儲存。
若整個資料列能放入索引節點中(如同 COMPACT
和 REDUNDANT
格式),DYNAMIC
列格式會保持儲存整個資料列的效率。但 DYNAMIC
列格式會避免因長資料行的龐大資料位元組填滿 B 樹節點的問題。DYNAMIC
列格式的基礎概念是,如果長資料值的一部分被儲存在頁外,通常將整個值儲存在頁外是最有效率的。使用 DYNAMIC
格式,較短的資料行可能仍會保留在 B 樹節點中,從而最大限度地減少給定資料列所需的溢出頁數量。
DYNAMIC
列格式支援最多 3072 位元組的索引鍵前綴。
使用 DYNAMIC
列格式的資料表可以儲存在系統表空間、每個資料表檔案的表空間和通用表空間中。若要將 DYNAMIC
資料表儲存在系統表空間中,請停用 innodb_file_per_table
並使用常規的 CREATE TABLE
或 ALTER TABLE
語句,或者使用 TABLESPACE [=] innodb_system
資料表選項搭配 CREATE TABLE
或 ALTER TABLE
。 innodb_file_per_table
變數不適用於通用表空間,也不適用於使用 TABLESPACE [=] innodb_system
資料表選項將 DYNAMIC
資料表儲存在系統表空間中。
DYNAMIC 列格式儲存特性
DYNAMIC
列格式是 COMPACT
列格式的變體。有關儲存特性,請參閱COMPACT 列格式儲存特性。
COMPRESSED
列格式提供與 DYNAMIC
列格式相同的儲存特性和功能,但增加了對資料表和索引資料壓縮的支援。
COMPRESSED
列格式使用與 DYNAMIC
列格式相似的內部細節進行頁外儲存,並在資料表和索引資料壓縮以及使用較小的頁面大小時,考量額外的儲存和效能。使用 COMPRESSED
列格式時,KEY_BLOCK_SIZE
選項控制叢集索引中儲存多少資料行資料,以及有多少資料放置在溢出頁面上。有關 COMPRESSED
列格式的更多資訊,請參閱第 17.9 節「InnoDB 資料表和頁面壓縮」。
COMPRESSED
列格式支援最多 3072 位元組的索引鍵前綴。
使用 COMPRESSED
列格式的資料表可以在每個資料表檔案的表空間或通用表空間中建立。系統表空間不支援 COMPRESSED
列格式。若要將 COMPRESSED
資料表儲存在每個資料表檔案的表空間中,必須啟用 innodb_file_per_table
變數。innodb_file_per_table
變數不適用於通用表空間。通用表空間支援所有列格式,但由於物理頁面大小不同,壓縮和未壓縮的資料表不能在同一個通用表空間中共存。有關更多資訊,請參閱第 17.6.3.3 節「通用表空間」。
壓縮列格式儲存特性
COMPRESSED
列格式是 COMPACT
列格式的變體。有關儲存特性,請參閱COMPACT 列格式儲存特性。
InnoDB
資料表的預設列格式由 innodb_default_row_format
變數定義,其預設值為 DYNAMIC
。當未明確定義 ROW_FORMAT
資料表選項或指定 ROW_FORMAT=DEFAULT
時,會使用預設列格式。
可以使用 CREATE TABLE
或 ALTER TABLE
語句中的 ROW_FORMAT
資料表選項明確定義資料表的列格式。例如
CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC;
明確定義的 ROW_FORMAT
設定會覆寫預設列格式。指定 ROW_FORMAT=DEFAULT
等同於使用隱式預設值。
可以動態設定 innodb_default_row_format
變數
mysql> SET GLOBAL innodb_default_row_format=DYNAMIC;
有效的 innodb_default_row_format
選項包括 DYNAMIC
、COMPACT
和 REDUNDANT
。系統表空間不支援 COMPRESSED
列格式,因此不能將其定義為預設值。它只能在 CREATE TABLE
或 ALTER TABLE
語句中明確指定。嘗試將 innodb_default_row_format
變數設定為 COMPRESSED
會傳回錯誤
mysql> SET GLOBAL innodb_default_row_format=COMPRESSED;
ERROR 1231 (42000): Variable 'innodb_default_row_format'
can't be set to the value of 'COMPRESSED'
當未明確指定 ROW_FORMAT
選項,或使用 ROW_FORMAT=DEFAULT
時,新建立的資料表會使用 innodb_default_row_format
變數定義的列格式。例如,以下 CREATE TABLE
語句會使用 innodb_default_row_format
變數定義的列格式。
CREATE TABLE t1 (c1 INT);
CREATE TABLE t2 (c1 INT) ROW_FORMAT=DEFAULT;
當未明確指定 ROW_FORMAT
選項,或使用 ROW_FORMAT=DEFAULT
時,重建資料表的作業會靜默地將資料表的列格式變更為由 innodb_default_row_format
變數定義的格式。
資料表重建作業包括使用 ALGORITHM=COPY
或 ALGORITHM=INPLACE
且需要重建資料表的 ALTER TABLE
作業。如需更多資訊,請參閱第 17.12.1 節「線上 DDL 作業」。OPTIMIZE TABLE
也是一種資料表重建作業。
以下範例示範了資料表重建作業,該作業會靜默地變更未明確定義列格式所建立之資料表的列格式。
mysql> SELECT @@innodb_default_row_format;
+-----------------------------+
| @@innodb_default_row_format |
+-----------------------------+
| dynamic |
+-----------------------------+
mysql> CREATE TABLE t1 (c1 INT);
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/t1' \G
*************************** 1. row ***************************
TABLE_ID: 54
NAME: test/t1
FLAG: 33
N_COLS: 4
SPACE: 35
ROW_FORMAT: Dynamic
ZIP_PAGE_SIZE: 0
SPACE_TYPE: Single
mysql> SET GLOBAL innodb_default_row_format=COMPACT;
mysql> ALTER TABLE t1 ADD COLUMN (c2 INT);
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME LIKE 'test/t1' \G
*************************** 1. row ***************************
TABLE_ID: 55
NAME: test/t1
FLAG: 1
N_COLS: 5
SPACE: 36
ROW_FORMAT: Compact
ZIP_PAGE_SIZE: 0
SPACE_TYPE: Single
在將現有資料表的列格式從 REDUNDANT
或 COMPACT
變更為 DYNAMIC
之前,請考慮以下潛在問題。
REDUNDANT
和COMPACT
列格式支援的最大索引鍵前綴長度為 767 個位元組,而DYNAMIC
和COMPRESSED
列格式支援的索引鍵前綴長度為 3072 個位元組。在複寫環境中,如果來源上的innodb_default_row_format
變數設定為DYNAMIC
,而複本上的設定為COMPACT
,則以下未明確定義列格式的 DDL 語句在來源上成功,但在複本上失敗CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 VARCHAR(5000), KEY i1(c2(3070)));
有關相關資訊,請參閱第 17.21 節「InnoDB 限制」。
如果來源伺服器上的
innodb_default_row_format
設定與目的地伺服器上的設定不同,則匯入未明確定義列格式的資料表會導致結構描述不符錯誤。如需更多資訊,請參閱第 17.6.1.3 節「匯入 InnoDB 資料表」。
若要判斷資料表的列格式,請使用 SHOW TABLE STATUS
mysql> SHOW TABLE STATUS IN test1\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 0
Avg_row_length: 0
Data_length: 16384
Max_data_length: 0
Index_length: 16384
Data_free: 0
Auto_increment: 1
Create_time: 2016-09-14 16:29:38
Update_time: NULL
Check_time: NULL
Collation: utf8mb4_0900_ai_ci
Checksum: NULL
Create_options:
Comment:
或者,查詢資訊結構描述 INNODB_TABLES
資料表
mysql> SELECT NAME, ROW_FORMAT FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME='test1/t1';
+----------+------------+
| NAME | ROW_FORMAT |
+----------+------------+
| test1/t1 | Dynamic |
+----------+------------+