InnoDB 作為 MySQL 預設的交易型存儲引擎,其文件 IO 子系統是效能的關鍵。本文基於 MySQL 5.6 源碼結構,深入剖析 os_file.*(底層 IO 抽象與非同步實現)與 fil0fil.*(表空間管理與 IO 調度)兩大核心模組,並說明 Native AIO、模擬 AIO 與 Direct I/O 的演進邏輯。
os_file.*)InnoDB 封裝了跨平台的基本文件操作,統一介面如下:
| 函數 | 功能 |
|---|---|
os_file_create_simple | 建立/開啟檔案(不重試) |
os_file_create | 建立/開啟檔案(失敗重試至成功) |
os_file_close | 關閉檔案 |
os_file_read / os_file_write | 讀/寫資料(Linux 預用 pread/pwrite,無則退回 lseek+read/write) |
os_file_flush | fsync 強制落盤 |
os_file_get_size / os_file_set_size | 取得/設定檔案大小 |
為避免同步 IO 阻塞工作執行緒,InnoDB 實作了自己的 AIO 框架:
io_setup/io_submit/io_getevents,效能最佳。os_aio_handler_thread 執行緒池,以 pthread_cond 模擬完成通知。/* 單一 IO 槽位 */
struct os_aio_slot_t {
bool is_read; // 讀/寫
ulint pos; // 陣列索引
bool reserved; // 是否被佔用
ulint len; // 傳輸長度
byte *buf; // 資料緩衝區
ulint type; // OS_FILE_READ / OS_FILE_WRITE
ulint offset, offset_high; // 檔案偏移 (64-bit)
os_file_t file; // 檔案控制代碼
char *name; // 檔案名 (除錯用)
bool io_already_done; // 模擬 AIO 用
#ifdef POSIX_ASYNC_IO
struct aiocb control; // POSIX AIO 控制塊
#endif
};
/* IO 槽位陣列 (分段管理) */
struct os_aio_array_t {
os_mutex_t mutex; // 保護陣列
os_event_t not_full; // 有空槽位訊號
os_event_t is_empty; // 陣列空訊號
ulint n_slots; // 總槽位數
ulint n_segments; // 區段數 (每區段對應一組 handler thread)
ulint n_reserved; // 已使用槽位數
os_aio_slot_t *slots; // 槽位陣列
os_event_t *events; // 事件陣列
};

fil0fil.*)InnoDB 定義三層管理結構:
ibdata1、ib_logfile*、ibtmp*),對應 space_id,含雜湊索引、RW-Latch、Insert Buffer 指標等。.ibd 或 ibdata 分檔),管理檔案控制代碼、頁數、髒頁標記、LRU 節點。struct fil_system_t {
mutex_t mutex;
hash_table_t *spaces; // space_id -> fil_space_t
ulint n_open_pending; // 正在進行 IO 的檔案數
ulint max_n_open; // 最大開啟檔案數 (預設 500)
os_event_t can_open; // 可開啟新檔案訊號
UT_LIST_BASE_NODE_T(fil_node_t) LRU; // 最近使用檔案
UT_LIST_BASE_NODE_T(fil_node_t) space_list;// 所有 fil_node 串聯
};
struct fil_space_t {
char *name;
ulint id; // space_id
ulint purpose; // FIL_TABLESPACE / FIL_LOG / FIL_ARCH_LOG
ulint size; // 頁數
rw_lock_t latch; // 並發保護
ibuf_data_t *ibuf_data; // Insert Buffer
UT_LIST_BASE_NODE_T(fil_node_t) chain; // 屬於此 space 的檔案鏈
};
struct fil_node_t {
char *name; // 檔案路徑
bool open; // 是否開啟
os_file_t handle; // 檔案控制代碼
ulint size; // 頁數 (16KB/page)
ulint n_pending; // 排隊 IO 數
bool is_modified; // 有髒頁待刷盤
UT_LIST_NODE_T(fil_node_t) chain;
UT_LIST_NODE_T(fil_node_t) LRU;
};

fil_io(),判斷 IO 類型與檔案開啟模式。n_open_pending:若超過 max_n_open * 3/4 喚醒所有 AIO handler 處理並 sleep;若達 max_n_open 等待 can_open 訊號。fil_space_t 與 fil_node_t,若檔案未開啟則開啟(LRU 滿時淘汰尾部節點關檔)。os_aio 執行非同步 IO,等待完成。can_open 訊號喚醒等待者。
繞過作業系統頁面快取,直接在使用者空間緩衝區與磁碟間傳輸,減少記憶體拷貝與快取污染。InnoDB 透過 os_file_create 以 O_DIRECT 旗標開啟資料檔,Redo Log 預設仍用緩衝 IO(可由 innodb_flush_log_at_trx_commit 控制)。Oracle、OceanBase 等亦廣泛採用此技術。
InnoDB IO 子系統涉及作業系統 IO 模型、鎖同步、記憶體管理、LRU 淘汰等核心技術。建議:
gdb 或 perf 做單元測試驗證呼叫鏈。fil_io() → os_aio → handler thread 完整路徑。innodb_use_native_aio、innodb_max_dirty_pages_pct、innodb_io_capacity 等參數對 IO 調度的影響。