InnoDB 寫密集場景下,B+ 樹頁面分裂導致表空間文件 (`.ibd`) 頻繁擴展。每次擴展需獲取 **Mutex** 保護文件元數據,造成**性能抖動 (Jitter)**,嚴重影響寫入吞吐。
若預知表數據最終大小,**預先分配 (Pre-extend) 表空間文件**,避免運行時擴展爭用 Mutex。InnoDB 內部由 `FIL_IBD_FILE_INITIAL_SIZE` 常量控制初始大小 (默認較小),通過 `fil_create_new_single_table_tablespace()` 初始化。
修改 MySQL 源碼 (基於 5.5/5.6 版本),在 `CREATE TABLE` 語法中支持:
CREATE TABLE test (
id INT PRIMARY KEY,
data VARCHAR(100)
) ENGINE=InnoDB DATAFILE_INITIAL_SIZE=100000; -- 頁數 (16KB/頁),約 1.5GB
// 原代碼
error = fil_create_new_single_table_tablespace(
space, path_or_name, is_path,
flags == DICT_TF_COMPACT ? 0 : flags,
FIL_IBD_FILE_INITIAL_SIZE); // 固定常量
// 修改後:優先使用表定義的 datafile_initial_size,小於默認值則用默認值
error = fil_create_new_single_table_tablespace(
space, path_or_name, is_path,
flags == DICT_TF_COMPACT ? 0 : flags,
table->datafile_initial_size < FIL_IBD_FILE_INITIAL_SIZE ?
FIL_IBD_FILE_INITIAL_SIZE : table->datafile_initial_size);
// create_table_def 階段保存參數
form->datafile_initial_size = create_info->datafile_initial_size;
// 建表時傳遞給 dict_table
table->datafile_initial_size = form->datafile_initial_size;
struct dict_table_struct {
// ... 現有字段
uint datafile_initial_size; // 新增:表空間初始大小 (頁數)
};
class TABLE {
// ...
uint datafile_initial_size; // CREATE_INFO 傳遞用
};
%token DATAFILE_INITIAL_SIZE_SYM
// create_option 規則中添加
| DATAFILE_INITIAL_SIZE_SYM opt_equal ulonglong_num
{
if ($3 > UINT_MAX32)
Lex->create_info.datafile_initial_size = UINT_MAX32;
else
Lex->create_info.datafile_initial_size = $3;
Lex->create_info.used_fields |= HA_CREATE_USED_DATAFILE_INITIAL_SIZE;
}
// 關鍵字註冊
{ "DATAFILE_INITIAL_SIZE", SYM(DATAFILE_INITIAL_SIZE_SYM) },
#define HA_CREATE_USED_DATAFILE_INITIAL_SIZE (1L << 22)
// HA_CREATE_INFO 結構體新增
uint datafile_initial_size;
{ "DATAFILE_INITIAL_SIZE", SYM(DATAFILE_INITIAL_SIZE_SYM) },
-- 建表指定 10 萬頁 (約 1.6GB)
CREATE TABLE t1 (id INT PRIMARY KEY) ENGINE=InnoDB DATAFILE_INITIAL_SIZE=100000;
-- 查看文件大小 (Linux)
ls -lh /var/lib/mysql/db/t1.ibd
# -rw-r----- 1 mysql mysql 1.6G ... t1.ibd <-- 瞬時創建 1.6GB 文件,無運行時擴展
-- MySQL 8.0.23+
CREATE TABLE t1 (id INT PRIMARY KEY)
TABLESPACE ts_1
AUTOEXTEND_SIZE=64M; -- 每次擴展 64MB,減少擴展頻率
| 場景 | 建議 |
|---|---|
| 歷史版本 (5.5/5.6) 高寫入大表 | 應用此 Patch 或預先 `INSERT` 大量佔位數據撐大文件 |
| MySQL 5.7+ | 使用 `innodb_data_file_path` 規劃系統表空間 / 文件表空間預分配 |
| MySQL 8.0.23+ | **首選 `AUTOEXTEND_SIZE`** 控制擴展粒度,配合 `innodb_flush_log_at_trx_commit=2` 降低抖動 |
| 雲托管 RDS | 通常不支持文件級控制,依賴實例規格 IOPS |
本文展示了從源碼層面解決 InnoDB 表空間擴展抖動的經典 Patch:**引入 `DATAFILE_INITIAL_SIZE` 表選項,修改詞法解析、SQL 層傳遞、InnoDB 字典結構、表空間創建入口**。雖現代版本已有更完善原生方案 (`AUTOEXTEND_SIZE`),但此 Patch 體現的**「預分配避免運行時鎖競爭」**核心思想,仍是數據庫內核優化的通用原則。