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結構化。下面依次來介紹這兩個方面的內容.
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;內存結構關系圖:
在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來做控制,以下是它的控制函數:
其結構定義如下:
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; };
了解了他們三者的基本定義後,那他們之間的關系是怎麼的?不用文字敘述,看下面的內存結構關系圖:
在了解了他們之間的基本關系後,那麼一個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操作提交。 流程圖如下: