資料表的列格式決定了其列的實際儲存方式,進而影響查詢和 DML 操作的效能。由於單個磁碟頁面可以容納更多列,因此查詢和索引查找可以更快地工作,緩衝池中所需的快取記憶體更少,並且寫出更新的值所需的 I/O 也更少。
每個資料表中的資料都分為多個頁面。組成每個資料表的頁面都排列在稱為 B 樹索引的樹狀資料結構中。資料表資料和次要索引都使用這種結構。代表整個資料表的 B 樹索引稱為叢集索引,它是根據主索引鍵欄進行組織的。叢集索引資料結構的節點包含該列中所有欄的值。次要索引結構的節點包含索引欄和主索引鍵欄的值。
可變長度欄是欄值儲存在 B 樹索引節點中的規則例外。太長而無法放入 B 樹頁面的可變長度欄會儲存在單獨配置的磁碟頁面上,這些頁面稱為溢位頁面。這些欄稱為頁外欄。頁外欄的值儲存在溢位頁面的單向連結清單中,每個這樣的欄都有自己的單個或多個溢位頁面清單。根據欄的長度,所有或部分可變長度欄的值都會儲存在 B 樹中,以避免浪費儲存空間並必須讀取單獨的頁面。
InnoDB
儲存引擎支援四種列格式:REDUNDANT
、COMPACT
、DYNAMIC
和 COMPRESSED
。
表 17.14 InnoDB 列格式概觀
列格式 | 精簡儲存特性 | 增強的可變長度欄儲存 | 大型索引鍵前置詞支援 | 壓縮支援 | 支援的表空間類型 |
---|---|---|---|---|---|
REDUNDANT |
否 | 否 | 否 | 否 | 系統、每個資料表獨立、一般 |
COMPACT |
是 | 否 | 否 | 否 | 系統、每個資料表獨立、一般 |
DYNAMIC |
是 | 是 | 是 | 否 | 系統、每個資料表獨立、一般 |
COMPRESSED |
是 | 是 | 是 | 是 | 每個資料表獨立、一般 |
以下主題說明列格式儲存特性,以及如何定義和判斷資料表的列格式。
REDUNDANT
格式提供與舊版 MySQL 的相容性。
使用 REDUNDANT
列格式的資料表,會將可變長度欄位值(VARCHAR
、VARBINARY
,以及 BLOB
和 TEXT
類型)的前 768 個位元組儲存在 B 樹節點內的索引記錄中,其餘部分則儲存在溢出頁面中。長度大於或等於 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
列格式的資料表,會將可變長度欄位值(VARCHAR
、VARBINARY
,以及 BLOB
和 TEXT
類型)的前 768 個位元組儲存在 B 樹節點內的索引記錄中,其餘部分則儲存在溢出頁面中。長度大於或等於 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
。為
CHAR(
保留最少N
)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 個位元組,則會內聯儲存。
DYNAMIC
列格式會維持如果整個列適合儲存在索引節點中的效率(如同 COMPACT
和 REDUNDANT
格式),但 DYNAMIC
列格式避免了使用長欄位的大量資料位元組填滿 B 樹節點的問題。DYNAMIC
列格式基於這樣一個觀點:如果將長資料值的一部分儲存在頁面外,通常最有效的方法是將整個值儲存在頁面外。使用 DYNAMIC
格式,較短的欄位很可能保留在 B 樹節點中,從而最大限度地減少給定列所需的溢出頁面數量。
DYNAMIC
列格式支援最大 3072 個位元組的索引鍵字首。
使用 DYNAMIC
列格式的表格可以儲存在系統表格空間、每個表格檔案的表格空間和一般表格空間中。若要將 DYNAMIC
表格儲存在系統表格空間中,可以停用 innodb_file_per_table
並使用一般的 CREATE TABLE
或 ALTER TABLE
陳述式,或者搭配 CREATE TABLE
或 ALTER TABLE
使用 TABLESPACE [=] innodb_system
表格選項。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:
或者,查詢 Information Schema INNODB_TABLES
表格
mysql> SELECT NAME, ROW_FORMAT FROM INFORMATION_SCHEMA.INNODB_TABLES WHERE NAME='test1/t1';
+----------+------------+
| NAME | ROW_FORMAT |
+----------+------------+
| test1/t1 | Dynamic |
+----------+------------+