innodb作為數據庫引擎,自然少不了對文件的操作,在innodb中所有需要持久化的信息都需要文件操作,例如:表文件、重做日志文件、事務日志文件、備份歸檔文件等。innodb對文件IO操作可以是煞費苦心,其主要包括兩方面,一個是對異步io的實現,一個是對文件操作管理和io調度的實現。在MySQL-5.6版本的innodb還加入了DIRECT IO實現。做了這么多無非是優化io操作的性能。在innodb的文件IO部分中,主要實現集中在os_file.*和fil0fil.*兩個系列的文件當中,其中os_file*是實現基本的文件操作、異步IO和模擬異步IO。fil0fil.*是對文件io做系統的管理和space結構化。下面依次來介紹這兩個方面的內容.
## 1.系統文件IO
在innodb中,文件的操作是比較關鍵的,innodb封裝了基本的文件操作,例如:文件打開與關閉、文件讀寫以及文件屬性訪問等。這些是基本的文件操作函數封裝。在linux文件的讀寫方面,默認是采用pread/pwrite函數進行讀寫操作,如果系統部支持這兩個函數,innodb用lseek和read、write函數聯合使用來達到效果. 以下是innodb文件操作函數:
? os_file_create_simple ? ? ? ? ? ? ? ? ? ? ? ?創建或者打開一個文件
? os_file_create ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 創建或者打開一個文件,如果操作失敗會重試,直到成功
? os_file_close ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 關閉打開的文件
? os_file_get_size ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 獲得文件的大小
? os_file_set_size ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 設置文件的大小并以0填充文件內容
? os_file_flush ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?將寫的內容fsync到磁盤
? os_file_read ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?從文件中讀取數據
? os_file_write ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 將數據寫入文件
innodb除了實現以上基本的操作以外,還實現了文件的異步IO模型,在Windows下采用的IOCP模型來進行處理(具
體可以見網上的資料),在linux下是采用aio來實現的,有種情況,一種是通過系統本身的aio機制來實現,還有一種是
通過多線程信號模擬來實現aio.這里我們重點來介紹,為了實現aio,innodb定義了slot和slot array,具體數據結構如下:
~~~
typedef struct os_aio_slot_struct
{
ibool is_read; /*是否是讀操作*/
ulint pos; /*slot array的索引位置*/
ibool reserved; /*這個slot是否被占用了*/
ulint len; /*讀寫的塊長度*/
byte* buf; /*需要操作的數據緩沖區*/
ulint type; /*操作類型:OS_FILE_READ OS_FILE_WRITE*/
ulint offset; /*當前操作文件偏移位置,低32位*/
ulint offset_high; /*當前操作文件偏移位置,高32位*/
os_file_t file; /*文件句柄*/
char* name; /*文件名*/
ibool io_already_done; /*在模擬aio的模式下使用,TODO*/
void* message1;
void* message2;
#ifdef POSIX_ASYNC_IO
struct aiocb control; /*posix 控制塊*/
#endif
}os_aio_slot_t;
typedef struct os_aio_array_struct
{
os_mutex_t mutex; /*slots array的互斥鎖*/
os_event_t not_full; /*可以插入數據的信號,一般在slot數據被aio操作后array_slot有空閑可利用的slot時發送*/
os_event_t is_empty; /*array 被清空的信號,一般在slot數據被aio操作后array_slot里面沒有slot時發送這個信號*/
ulint n_slots; /*slots總體單元個數*/
ulint n_segments; /*segment個數,一般一個對應n個slot,n = n_slots/n_segments,一個segment作為aio一次的操作范圍*/
ulint n_reserved; /*有效的slots個數*/
os_aio_slot_t* slots; /*slots數組*/
os_event_t* events; /*slots event array,暫時沒弄明白做啥用的*/
}os_aio_array_t;
~~~
內存結構關系圖:

## 2.文件管理的內存結構
在innodb中定義三種文件類型:表空間文件(ibdata*)、重做日志文件(ib_logfile*)和歸檔文件(ib_arch_log*)。一般innodb在運行的過程中,會同時打開很多個文件,這就要求對文件進行系統的管理和控制。在innodb中定義了一套基于fil_system_t、fil_space_t和fil_node_t的內存管理結構。每個文件對應的是一個fil_node_t,fil_node是存儲的最小單元,多個同一模塊的fil_node組成一個fil_space_t,所有的space組成一個fil_system_t,在innodb引擎里,只有一個fil_system_t對象。
fil_system_t管理著全局的文件操作資源,例如:文件打開的數量、打開文件的信號控制、fil_space_t的管理和索引等。以下是fil_system_t的結構定義:
~~~
typedef struct fil_system_struct
{
mutex_t mutex; /*file system的保護鎖*/
hash_table_t* spaces; /*space的哈希表,用于快速檢索space,一般是通過space id查找*/
ulint n_open_pending; /*當前有讀寫IO操作的fil_node個數*/
ulint max_n_open; /*最大允許打開的文件個數*/
os_event_t can_open; /*可以打開新的文件的信號*/
UT_LIST_BASE_NODE_T(fil_node_t) LRU; /*最近被打開操作過的文件,用于快速定位關閉的fil_node*/
UT_LIST_BASE_NODE_T(fil_node_t) space_list; /*file space的對象列表*/
}fil_system_t;
~~~
值得注意的是space的哈希表和LRU,這里為什么會出現用hash table來索引space呢?因為在實際的數據庫系統中,fil_space_t是會非常多的,用哈希表能快速定位到需要操作的fil_space_t。LRU是用于保存最近被打開和被操作過的fil_node,為了避免頻發的關閉和打開文件,LRU保存一定數量(500)的最近打開過的文件,這樣可以提高系統的效率。
fil_space_t是用于管理同一模塊的file_node,上層模塊操作文件不是以文件名來做操作關聯的,而是用space_id,
也就是說,所有的文件操作是通過space為單位進行操作的。fil_space支持三種類型,分別是:
? ? ? FIL_TABLESPACE ? ? ? ? ? ? ? ?表空間space
? ? ? FIL_LOG ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? 重做日志space
? ? ? FIL_ARCHI_LOG ? ? ? ? ? ? ? ? ? 歸檔日志space
fil_space_t的定義如下:
~~~
struct fil_space_struct
{
char* name; /*space名稱*/
ulint id; /*space id*/
ulint purpose; /*space的類型,主要有space table, log file和arch file*/
ulint size; /*space包含的頁個數*/
ulint n_reserved_extents; /*預留的頁個數*/
hash_node_t hash; /*chain node的HASH表*/
rw_lock_t latch; /*space操作保護鎖,用于多線程并發*/
ibuf_data_t* ibuf_data; /*space 對應的insert buffer*/
ulint magic_n; /*魔法校驗字*/
UT_LIST_BASE_NODE_T(fil_node_t) chain;
UT_LIST_NODE_T(fil_space_t) space_list;
};
~~~
fil_space通常是由一組文件組成,例如重做日志,一般是有3個文件組成一個group space用于重做日志記錄。space通過成員latch可以支持多線程并發的。在innodb文件操作中,主要是通過space來做控制,以下是它的控制函數:
fil_space_create ? ? ? ? ? ? ? ? ? ? ? ?創建一個fil_space
fil_space_free ? ? ? ? ? ? ? ? ? ? ? ? ? ?銷毀一個fil_space
fil_space_truncate_start ? ? ? ? ?從space中刪除fil_node,刪除的總數據長度為trunc_len
fil_node_create ? ? ? ? ? ? ? ? ? ? ? ? ?創建一個fil_node并加入到對應的space當中
fil_space_get_size ? ? ? ? ? ? ? ? ? ?獲得space的空間大小,以page為單位記
fil_io ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?指定space的io操作
fil_aio_wait ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?aio異步方式的io操作等待,并根據完成狀態更新space狀態
fil_flush ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?指定space進行數據刷盤
fil_node_t是對單個文件進行管理,主要是管理文件的打開狀態、文件句柄信息、文件的page數量和更新狀態等。
其結構定義如下:
~~~
struct fil_node_struct
{
char* name; /*文件路徑名*/
ibool open; /*文件是否被打開*/
os_file_t handle; /*文件句柄*/
ulint size; /*文件包含的頁個數,一個頁是16K*/
ulint n_pending; /*等待讀寫IO操作的個數*/
ibool is_modified; /*是否有臟也存在,flush是根據這個標志進行刷盤的*/
ulint magic_n; /*魔法校驗字*/
UT_LIST_NODE_T(fil_node_t) chain;
UT_LIST_NODE_T(fil_node_t) LRU;
};
~~~
值得注意的是當外部調用了fil_flush時,判斷一個fil_node是否需要刷盤的必要條件是:
? 文件必須是打開的 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?open = TRUE
? 文件存在內存和硬盤數據不一致 ? ? ? ? ? ?is_modified = TRUE
了解了他們三者的基本定義后,那他們之間的關系是怎么的?不用文字敘述,看下面的內存結構關系圖:

在了解了他們之間的基本關系后,那么一個io操作是怎么進行的?在這個模型里,一個io操作提交和被運行是比較復雜的。具體流程如下:
? 1.外部模塊提交一個fil_io, 先會進行基本的io操作類型的判斷和文件打開方式的判斷。
? 2.然后進行對正在進行io操作的計數做判斷,如果正在進行的io數量 > 最大文件打開數量的四分之三,喚醒所有aio的操作線程進行io處理,并進行sleep等待。
? 3.如果正在進行的io數量 =?最大文件打開數量,喚醒所有的aio操作線程進行io處理,并等待fil_system_t的can_open信號。
? 4.如果不滿足2和3,找到需要受理io操作的space和node,并打開node對應的文件,打開文件時會對打開文件數量限制做判斷,如果當前打開文件操作io的數量 + LRU里已經打開文件的數量>=?最大文件打開數量時,會取出LRU中最后一個fil_node進行文件關閉。然后在對新的io操作的fil_node文件進行打開。
? 5.fil_node文件打開后,調用os_aio進行io操作提交,然后等待io操作完成
? 6. io操作完成后,將完成io操作的fil_node放入LRU的第一個位置,并更改對應的fil_system/fil_space/fil_node的狀態,最后觸發一個fil_system的can open信號。
? 7.監聽can_open的線程收到這個信號后,會跳到第4步進行自己的io操作提交。
流程圖如下:

## 3總結
總體來說,innodb的文件IO涉及到知識面很多,可以能短時間無法完全理解透徹,一般在閱讀源碼的時候可以做一些基本的單元測試,這樣有助于理解。弄清楚innodb的文件IO操作是非常有必要的,因為文件IO操作模塊直接影響對innodb的日志系統的理解、表空間系統的理解。而且Innodb在文件IO模塊的改進還是比較大的,尤其是引入Direct IO后。Direct IO很多數據庫都在用這個技術,除了innodb,oracle和淘寶的oceanbase都使用了這個技術, 關于Direct IO網絡上資料很多,可以自行結合MySQL-5.6的innodb來做研究。