CREATE TABLE 看似簡單,實則觸發 MySQL Server 層與存儲引擎層複雜協作:語法解析、權限檢查、鎖管理、FRM/IBD 文件創建、數據字典註冊、索引構建、外鍵處理。本文基於 MySQL 5.1/5.5/5.7/8.0 核心調用鏈,深度解析三種創建模式 (CREATE TABLE、CREATE TABLE ... LIKE、CREATE TABLE ... SELECT) 的內核流程差異,為內核研發、極致優化、故障診斷提供源碼級視角。
do_command() (sql_parse.cc)
→ dispatch_command()
→ mysql_parse() -- 語法解析、AST 生成
→ mysql_execute_command()
→ mysql_create_table() (sql_table.cc)
→ mysql_create_table_no_lock() -- 核心邏輯入口
→ ha_create_table() (handler.cc) -- 存儲引擎接口
→ handler::ha_create()
→ ha_innodb::create() (ha_innodb.cc)
→ create_table_def() -- InnoDB 內部表定義構建
→ row_create_table_for_mysql() (row0mysql.c)
→ dict_table_create() -- 數據字典註冊
→ create_clustered_index_when_no_primary() -- 隱式主鍵生成
→ row_create_index_for_mysql() -- 二級索引創建
LOCK_open:保護表緩存、防止並發 DDL 衝突。MDL (Metadata Lock):5.5+ 引入,MDL_EXCLUSIVE 阻塞並發 DML/DDL,MDL_SHARED_READ 允許並發查詢。creating_table++:統計計數器 Com_create_table。| 階段 | CREATE TABLE |
CREATE TABLE ... LIKE |
CREATE TABLE ... SELECT |
|---|---|---|---|
| 解析入口 | mysql_create_table() | mysql_create_like_table() | mysql_execute_command() → handle_select() |
| 源表處理 | 無 | open_tables() 打開源表,持有 LOCK_open 直到 FRM 複製完成 | open_and_lock_tables() 加表鎖 (讀鎖) 直到整個 Statement 完成 |
| FRM 創建 | mysql_create_frm() 根據 AST 構建 | my_copy() 直接複製源表 .frm 文件 (或 mysql_create_like_schema_frm() 處理 I_S 表) | create_table_from_items() 根據 SELECT 列項構建 FRM,在 JOIN::prepare() 階段完成 |
| 存儲引擎創建 | ha_create_table() 正常流程 | 同正常流程 (引擎層無感知 LIKE) | 同正常流程,但在 JOIN::exec() 階段邊查詢邊寫入 (select_insert::send_data() → write_record()) |
| 數據填充 | 無 | 無 | SELECT 結果集逐行寫入新表 (NLJ 內層) |
| 鎖持有時間 | 短 (僅 FRM 創建階段) | 中 (持有 LOCK_open 複製 FRM) | **長** (持有源表讀鎖直到所有數據寫入完成) |
| 事務性 | 非事務 (隱式提交) | 非事務 | **隱式事務** (5.7+ 支援原子性:失敗自動回滾創建的表) |
mysql_create_frm() — Server 層表定義持久化TABLE 對象,填充 Field 數組、KEY 數組、PARTITION_INFO。pack_record() 序列化為二進制格式寫入 .frm (5.7) 或數據字典表 (8.0 mysql.tables, mysql.columns, mysql.indexes...)。ha_innodb::create() — InnoDB 表創建入口create_table_def() 構建 dict_table_t 內存對象 (列、索引、外鍵、約束)。create_clustered_index_when_no_primary() 自動生成 6 字節 GEN_CLUST_INDEX (隱式 Row ID) 作為聚簇索引。**這是為什麼強烈建議顯式定義主鍵** — 隱式主鍵無業務含義、佔用空間、不可被引用、熱點爭用風險。row_create_index_for_mysql() 創建二級索引 B+ 樹 (初始為空 Root Page)。row_table_add_foreign_constraints()。CREATE TABLE ... SELECT 的特殊性 — 在 SELECT 準備階段創建表此模式邏輯極其巧妙:
JOIN::prepare() 初始化 select_result 為 select_create 類型 (繼承鏈:select_result ← select_result_interceptor ← select_insert ← select_create)。select_create::prepare() → create_table_from_items() → mysql_create_table_no_lock() **在優化器準備階段就創建好目標表結構**。JOIN::exec() → do_select() → sub_select() → evaluate_join_record() → end_send() → select_insert::send_data() → write_record() → ha_write_row() **邊查詢邊寫入**。啟示:MySQL 將 CREATE TABLE ... SELECT 視為一種特殊的「查詢」,其輸出目標是新建表而非網絡流。這解釋了為什麼它需要對源表加鎖 (保證 SELECT 一致性)、且在 5.7 前非原子 (失敗留下空表)。
| 項目 | 5.7 及之前 | 8.0+ |
|---|---|---|
| 表定義存儲 | .frm 文件 (每表一個,Server 層管理) | InnoDB 系統表空間 mysql.ibd 內的數據字典表 (mysql.tables, mysql.columns, mysql.indexes, mysql.foreign_keys...) |
| DDL 原子性 | 無 (中間狀態可見、崩潰留孤兒文件) | 原子 DDL (事務化,失敗自動回滾,無可見中間態) |
| 崩潰恢復 | 需修復 .frm/.ibd 不一致 | 僅靠 Redo Log,字典表同樣受 WAL 保護 |
| 鎖機制 | LOCK_open + 文件系統鎖 | MDL (Metadata Lock) 統一管理,無文件操作 |
| INFORMATION_SCHEMA | 臨時表填充 (慢、佔內存) | 直接查詢數據字典表 (極快、實時) |
8.0 調用鏈變化:mysql_create_table() → dd::create_table() (Data Dictionary API) → dd::Table::create() → InnoDB 內部創建。Server 層不再操作 .frm 文件。
| 現象 | 可能源碼定位點 | 排查方向 |
|---|---|---|
| CREATE TABLE 卡住無響應 | mdl_lock_wait(), lock_wait_suspend_thread() | SHOW ENGINE INNODB STATUS 看鎖等待、MDL 持有者、是否有長事務阻塞 |
| ERROR 1005 (HY000): Can't create table (errno: 150) | row_table_add_foreign_constraints() | 外鍵引用表/列不存在、類型不匹配、引用表非 InnoDB、缺少索引 |
| CREATE TABLE ... SELECT 留下空表 | mysql_execute_command() 錯誤處理分支 | 5.7 前無原子性,SELECT 失敗不回滾創建;8.0 原子 DDL 解決 |
| 隱式主鍵導致熱點爭用 | dict_table_create() → create_clustered_index_when_no_primary() | 顯式定義業務主鍵 (自增/UUID/雪花 ID),避免 GEN_CLUST_INDEX 尾部插入熱點 |
CREATE TABLE 是 MySQL Server 層 (SQL 解析、權限、MDL、數據字典) 與存儲引擎層 (InnoDB 表定義、聚簇/二級索引構建、外鍵註冊) **邊界最清晰、交互最複雜** 的 DDL 操作之一。理解三種模式的調用鏈差異、8.0 原子 DDL 的架構重構、隱式主鍵的性能代價,能讓你在內核研發、極致優化、疑難故障診斷中佔據源碼級視角優勢。**最佳實踐:永遠顯式定義主鍵、避免 CREATE TABLE ... SELECT 大表操作 (改用 xtrabackup/導入導出)、升級 8.0 享受原子 DDL 與高性能數據字典。**