天天看點

表空間及組成表空間的實體檔案

相關檔案: fil0fil.h fil0fil.c

功能:對disk上的表空間及組成表空間的實體檔案進行管理(如建立,打開,關閉,删除,重命名等操作);對表空間中的頁在實體檔案上進行存取(io操作)。

introduction

表空間的實體組成

innodb在對資料庫檔案的管理上使用了類似oracle的表空間(tablespace)技術。表空間隻是邏輯上的管理方法,資料庫的存儲在實體上仍是按檔案進行。在innodb中有三種表空間:系統表空間(也被稱為共享表空間),重做日志表空間和獨立表空間。這三種表空間在實體上究竟分别指的是什麼呢?先來看下msyqld啟動時與這三種表空間相關的參數:

<col>

innodb_data_file_path

該參數與系統表空間相關:比如設定其為 innodb_data_file_path=ibdata1:2g; ibdata2:2g: ibdata3:2g:autoextend則系統表空間在實體上就由ibdata1, ibdata2, ibdata3這三個檔案組成,三個檔案在建立時都為1gb大小,在以後的時間裡前兩個檔案大小一直固定不變,最後一個檔案因有autoextend可以自增長。

note:系統表空間中的檔案可以設定成不在mysql data directory中,可以使它們分布在不同的disk以均衡負載。

innodb_log_files_in_group

innodb_log_file_size

這兩個參數與重做日志表空間相關: 比如設定innodb_log_files_in_group=3 則重做日志表空間在實體上由ib_logfile0, ib_logfile1, ib_logfile2共3個檔案組成,每個重做日志檔案的大小都相同,由innodb_log_file_size參數指定每個檔案的大小

innodb_file_per_table

該參數與獨立表空間相關:隻有啟用該參數才使用獨立表空間;當啟用了該參數後,建立的每個innodb表都自成一個表空間,稱為獨立表空間,這個表空間在實體上隻會由一個檔案組成,檔案名為database_name/table_name.ibd

note: 本篇文章的解讀是假設開啟了獨立表空間功能

三種表空間與實體檔案的關系示意圖:

表空間及組成表空間的實體檔案

總結:表空間在實體是由一到多個實體檔案組成的,而且獨立表空間有且隻有一個實體檔案,系統表空間與重做日志表空間根據對相應參數的設定可以有一個或者多個實體檔案。

表空間的邏輯組成

上一節說明了表空間的實體組成,而表空間在邏輯上是由一系列的連續編号的頁組成。示意圖如下:

表空間及組成表空間的實體檔案

上圖表示了一個含有n個頁的表空間,每一個頁都有一個page_no(也叫做頁偏移),page_no從0開始順序遞增。對于非壓縮頁,每個page的大小為16kb。

疑點解釋:與表空間和頁相關的另外兩個概念是段(segment)和區(extent),一種慣用說法是說表空間由多個segment組成,一個segment由多個extent組成,而1個extent又是由64個頁組成。而在上邊的說明中已指出表空間在邏輯上是由連續的頁組成的,那麼segment和extent又是從何而來呢?這就涉及到表空間的管理問題了,在innodb源代碼中這一塊是由fsp子產品負責的,對于該子產品這裡不深入解讀,此處需要明确的就是表空本質上隻是由連續編碼的頁組成,隻是innodb中為了管理以及性能上的需要,頁不隻是資料頁和索引頁,還有很多種其他頁類型,其中某些頁類型就專門用于存儲segment或extent資訊,讀取這些頁就可以知道這個segment或extent控制的頁範圍等資訊,進而引申出了segment和extent的概念。想要了解表空間對segment,extent和page的管理需要去解讀fsp子產品,這将放在以後進行解讀,本篇文章将專注于解讀fil子產品。

表空間的實體組成與邏輯組成間的關系--------檔案與頁的關系

至此已經說明了表空間的實體組成與邏輯組成,那麼表空間的實體組成與邏輯組成間又是什麼樣的關系呢?也就是說實體檔案與頁之間是什麼關系呢?它們間的關系當然是頁是存儲在實體檔案上啦,隻是對于獨立表空間而言,所有的頁都存儲在一個檔案上,而對于系統表空間和重做日志表空間而言,如果它們包含多個實體檔案的話,所有的頁會分布在不同的檔案上。示意圖如下:

需要注意的是在系統表空間和重做日志表空間裡,一個頁不能跨越一個檔案的末尾和随後一個檔案的開始,也即每個頁必須完整地存儲在某個檔案中。如果指定的這兩個表空間的實體檔案的大小不為page的整數倍時,每一個實體檔案都會按page大小進行截取,最後多餘的部分不被使用(由于在設定這二個表空間的實體檔案大小時通常都會以m或者g為機關,其必為page的整數倍,是以截取檔案這種操作并不會發生)。

note: innodb中的表空間支援壓縮功能,但是隻有獨立表空間可以被壓縮。對于非壓縮page,其大小為16kb,一個extent就為1mb。因而對于系統表空間和重做日志表空間來說,其頁大小始終是16kb,對獨立表空間來說,頁大小要看其是否采用了壓縮以及何中壓縮級别。

fil子產品資料結構解讀

上邊introduction部分所說的表空間都是位于disk上,innodb源代碼中與其緊密相關的子產品之一是fil子產品。fil子產品的主要功能是:對disk上的表空間及組成表空間的實體檔案進行管理(如建立,打開,關閉,删除,重命名等操作);對表空間中的頁在實體檔案上進行存取(io操作)。

要實作fil子產品所負責的上述功能,就需要在記憶體中建立一套用于管理disk上表空間的資料結構。 fil子產品為實作這個目的使用了三個資料結構:fil_space_t , fil_node_t 和fil_system_t (fil_space_t類型執行個體用space表示, fil_node_t執行個體用fil_node表示,fil_system_t執行個體用fil_system表示 )。space用于管理打開的對應表空間,fil_node用于管理對應的實體檔案,fil_system用于管理所有打開的表空間。

introduction部分提到的三種表空間類型在fil子產品源代碼裡被統稱為space,并且被劃分成兩種類型:fil_tablespace和fil_log。系統表空間和獨立表空間就屬于fil_tablespace類型,而重做日志表空間就屬于fil_log類型了。在fil_space_t類型中有一個unsigned long類型的成員名為id,表示每個打開的space的編号,編号0始終表示系統表空間,重做日志表空間的space id必須大于或者等于srv_log_space_first_id(0xfffffff0ul),但是目前版本的innodb中隻使用了一組重做日志組,其space id就為srv_log_space_first_id。 而獨立表空間的id則在(0, srv_log_space_first_id)之間。id可能的最大取值為0xfffffffful(上層通過32位位域對unsigned long類型限定的結果)。

fil_system對所有打開的表空間是通過space_list雙向連結清單來管理的,其中第一個節點必為系統表空間對應的space,第二個節點必為重做日志表空間對應的space。這兩個表空間在伺服器啟動後直到伺服器關閉一直存在且在雙向連結清單裡始終依次占據前邊兩個位置。獨立表空間在被建立或打開後對應的space就被建立并依次加入到該雙向連結清單的末尾。

對于每一個打開的space,它所管理的所有實體檔案在記憶體中的控制節點fil_node也會被建立。并且space通過chain雙向連結清單将這些fil_node進行連結。也就是說space和它控制的所有fil_node是同生同滅的。此外,對于系統表空間和重做日志表空間而言,它們的所有實體檔案在伺服器啟動好後到伺服器關閉整個時間裡都是出于打開狀态的。但是對于獨立表空間來說,即使space和那個唯一的fil_node在記憶體中被建立,但是fil_node所控制的檔案也可能處于關閉狀态,這種情況可出現于建立一個innodb類型的表但又未在随後使用該表的情況。

fil_system有一個成員是雙向連結清單頭結點lru,用于連結打開的某些實體檔案。那麼為什麼要使用lru,另外哪些檔案又會被連結到lru雙向連結清單中呢?由于作業系統的限制,一個程序能夠同時打開的檔案數是有限制的,比如在linux系統上使用uname -n會得到結果1024,表示一個程序同時最多隻能打開1024個檔案。在mysqld啟動時可以使用參數innodb_ open_files來設定innodb可以打開的最大檔案數(設定時不要超過os的限制),預設值為300。innodb_ open_files的值會被指派給fil_system的成員max_n_open,max_n_open就表示innodb能夠同時打開的檔案限制數目,另外fil_system中有個成員為n_open,用來記錄目前已打開的實體檔案數目。由于有max_n_open的限制,當有一個表數目非常多的資料庫且開啟了獨立表空間功能時,就必定不能同時打開存在的所有檔案了。為了解決這個問題,就使用了lru政策:将所有已打開且當且沒有io操作行将進行的獨立表空間的實體檔案挂在lru連結清單上,當已打開的檔案數目達到限制後,要打開其他獨立表空間檔案就需要進行置換,位于lru尾部檔案的将被首先關閉,然後新打開的獨立表空間檔案被加入lru頭部。

小結:lru連結清單上連結的fil_node節點必須滿足下列三個條件: (1) fil_node控制的檔案已打開,即fil_node-&gt;open為    true; (2) fil_node控制的檔案沒有io操作即将進行,即fil_node-&gt;n_pending需要為0; (3) fil_node控制的檔案必須是獨立表空間檔案,因為系統表空間和重做日志表空間的實體檔案需要一直被打開,是以不能被挂在lru上。

為了高效地查找表空間,fil_system中使用了兩個哈希表,一個是按space-&gt;id進行查找,一個是按space-&gt;name進行查找,前者最常用,對應的函數是fil_space_get_by_id。

fil_system還有個成員unflushed-space,将待重新整理的所有space用雙向連結清單連結起來,以用于同一重新整理。fil_system中的成員mutex用于保證線程互斥通路fil_system、fil_node、space所組成的整套套資料結構。由于ddl涉及的并發性并不高,是以隻是用一個mutex來保證整套資料結構的互斥通路性能影響不大。

資料流程

上邊已将fil子產品下涉及的資料結構做了主要說明,但fil_system、fil_node、space都還有很多其他資料成員上邊沒有介紹,具體資訊去檢視相應的源代碼。下邊将從資料流的角度來說明innodb在啟動、運作、關閉時如何與fil子產品進行互動的。

innodb在啟動時會調用innobase_start_or_create_for_mysql函數,在該函數裡有五個與fil子產品緊密相關的的函數會被依次調用:fil_init,open_or_create_data_files, open_or_create_log_file, fil_open_log_and_system_tablespace_files和dict_check_tablespaces_and_store_max_id。fil_init函數用于建立fil_system資料結構。open_or_create_data_files與系統表空間相關:它首先嘗試去建立innodb_data_file_path中指明的實體檔案,如果是裝好mysql伺服器後第一次啟動mysqld,則該建立是成功的,但是當實體檔案已經存在于disk上時,則建立會失敗,open_or_create_data_files函數就打開innodb_data_file_path中指明的檔案,檢查檔案的大小與innodb_data_file_path中指明的是否一樣,若一切成功,則關閉建立或者打開的檔案,然後調用fil_space_create為系統表空間建立space,調用fil_node_create為所有innodb_data_file_path中指明的檔案建立fil_node。open_or_create_log_file函數與open_or_create_data_files的操作過程相似,隻是它是針對于重做日志表空間。上一節說過系統表空間和重做日志表空間的實體檔案在innodb啟動好後始終都是打開的,是以這裡就調用了fil_open_log_and_system_tablespace_files函數來打開這些實體檔案。然後innobase_start_or_create_for_mysql函數會調用dict_check_tablespaces_and_store_max_id函數,dict_check_tablespaces_and_store_max_id函數的作用是掃描data dictionary(所有.ibd檔案的表空間id,表空間name等資訊都會存儲在這裡),得到獨立表空間的space id和space name,然後調用fil_open_single_table_tablespace函數來為獨立表空間建立space和fil_node。至此innodb伺服器就啟動好了,建立好後的資料結構的示意圖如下圖所示(這裡忽略了哈希表及其他一些資料成員)。要注意的是innodb啟動後所有表空間的space和所有實體檔案的fil_node都會被建立,但是獨立表空間的.ibd 檔案沒是處于關閉狀态的,所有最初lru也是為空的。

那麼.ibd檔案在什麼時候才被打開呢?這就要提到fil子產品下的fil_io函數。fil_io函數是一個非常重要的函數,在很多地方都需要調用該函數(比如向buffer pool中讀取頁),它在fil子產品中負責對表空間中的頁在實體檔案上進行存取。當fil_io函數被調用來對獨立表空間進行io操作時,會根據space_id會去檢視對應的.ibd檔案是否打開,若沒有打開則會首先使用fil_node_open_file去打開.ibd檔案并挂在lru首部。然後fil_node_prepare_for_io函數又會将其從lru中取下,以準備進行io操作;當io操作完成後,fil_node_complete_io會被調用并将.ibd檔案再次挂到lru首部。

在innodb運作過程中,若進行innodb類型表的ddl操作時,也會調用到fil子產品中相應的函數。create一個innodb表時會調用到fil_create_new_single_table_tablespace函數,delete一個innodb表時會調用到fil_delete_tablespace,rename一個innodb表時會調用到fil_rename_tablespace函數。fil_create_new_single_table_tablespace首先會去建立.ibd檔案,然後關閉該.ibd檔案,再在記憶體中建立相應的space和fil_node。fil_rename_tablespace函數首先需要關閉相應的.ibd檔案,然後根據space_id找到space,對該space的space-&gt;name和相應的fil_node-&gt;name進行重命名,最後再對.ibd檔案重命名,但是重命名後的.ibd檔案不再打開。fil_delete_tablespace函數首先根據space_id找到space,然後銷毀space和相應的fil_node,最後再删除.ibd檔案。

在innodb關閉時,innobase_shutdown_for_mysql函數會先後調用logs_empty_and_mark_files_at_shutdown和fil_close函數。logs_empty_and_mark_files_at_shutdown函數會調用到fil子產品的fil_close_all_files函數,fil_close_all_files會從頭開始周遊fil_system-&gt;space_list連結清單,然後關閉每個space上的檔案,再銷毀space和space控制的所有fil_node。是以fil_close_all_files函數執行完後fig.4中的資料結構就隻剩下fil_system了,銷毀fil_system就是通過調用fil_close函數來完成了。

繼續閱讀