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
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
瞭解了他們三者的基本定義後,那他們之間的關係是怎麼的?不用文字敘述,看下面的內存結構關係圖: