文章目錄:
寫在前面的話InnoDB的儲存結構Tablespace常見的表空間SegmentExtentPage什麼是off-pageInnoDB的檔案儲存格式寫在前面的話你有沒有想過這樣一個問題:我們的資料在MySQL中是如何存放的?它是以什麼樣的組織方式存放在我們磁碟中的?
我們知道,資料是存放在表裡面的,在表裡面是一行一行存在的。那麼這一行一行的資料怎麼樣在磁碟中存放的呢?表又是如何在磁碟上存放的?讀完下面的文章,你就會對這個問題整體的認識。
InnoDB的儲存結構資料是放在表空間tablesapce中的,而表空間是段segment組成的,段又是由區extent組成的,區又是由頁page組成的。page裡面放的就是一行一行的資料。這樣就組成了MySQL中innodb的儲存結構。如下圖所示:
Tablespacetablespace就是我們平時所說的表空間。它是一個物理概念,對應到磁碟上,就是一個個資料檔案。例如在我的MySQL的安裝目錄下面有一個名稱為feng的資料庫,該資料庫下的表空間如下所示:
root@test:/var/lib/mysql/feng# pwd/var/lib/mysql/feng --------------------->這是我的資料庫目錄,資料名稱為:fengroot@test:/var/lib/mysql/feng# ls -lstrtotal 1464 4 -rw-r----- 1 mysql mysql 67 Dec 6 14:24 db.opt 12 -rw-r----- 1 mysql mysql 8674 Dec 24 10:51 t_innodb.frm # 表結構定義檔案 96 -rw-r----- 1 mysql mysql 98304 Dec 24 10:54 t_innodb.ibd # 表空間檔案,裡面存放資料和索引 12 -rw-r----- 1 mysql mysql 8674 Dec 24 10:51 t_myisam.frm # 表結構定義檔案 4 -rw-r----- 1 mysql mysql 2048 Dec 24 10:54 t_myisam.MYI # 表索引檔案 4 -rw-r----- 1 mysql mysql 168 Dec 24 10:54 t_myisam.MYD # 表空間檔案,裡面存資料root@test:/var/lib/mysql/feng#
從上面我們可以看出innodb儲存引擎的表空間和myisam儲存引擎的表空間,有一點不一樣:innodb儲存引擎的表空間對應的資料檔案和索引是放在一個檔案中的,而myisam儲存引擎的表對應的資料檔案和索引檔案是兩個分開的資料檔案,這也是innodb表又稱為IOT,索引組織表的一個原因,它的資料和索引是存放在一個數據檔案中的。
這裡對應的一個個資料檔案.ibd和.MYD結尾的檔案就是一個個表空間。我們可以看出這裡面是一個表對應一個表空間。不同的表他們的表空間是分開的。並不像Oracle那樣多個表共享一個表空間資料檔案。其實在MySQL中也有和Oracle類似的儲存方式,多個表共享一個表空間檔案。這個是通引數innodb_file_per_table來控制的。
如下是檢視MySQL中當前表空間檔案是否獨立的方式,這個引數是從MySQL5.6之後的版本才支援的,在5.6之前的版本中,是不支援獨立表空間設定的,和Oracle一樣多個表共享一個表空間資料檔案。
mysql> show variables like 'innodb_file_per_table'; /* 當該引數為ON時,表示每一個表單獨一個表空間檔案;如果為OFF,表示多個表共享一個表空間檔案。 */+-----------------------+-------+| Variable_name | Value |+-----------------------+-------+| innodb_file_per_table | ON |+-----------------------+-------+1 row in set (0.07 sec)mysql>
常見的表空間我們經常遇到的表空間可以參考MySQLInnodb儲存引擎的儲存架構圖:
innodb-architecture.png
從圖中我們可以看到我們經常遇到的表空間有如下幾類:
System Tablespace:系統表空間,對應到磁碟上面的資料檔案就是/var/lib/mysql/ibdata1,如下:root@test:/var/lib/mysql# ls -lstr ibdata*77824 -rw-r----- 1 mysql mysql 79691776 Dec 28 14:32 ibdata1root@test:/var/lib/mysql#
Undo Tablespace:回滾表空間,預設這個空間是和系統表空間共用一個表空間的,它不會單獨存在,和ibdata1系統表空間檔案存在一起。但是在MySQL5.6版本以後,支援單獨配置回滾表空間了。可以為其單獨配置,使用引數innodb_undo_tablespaces來配置使用幾個回滾表空間。如果安裝MySQL的時候沒有配置回滾表空間,那麼查詢的結果如下:
mysql> show variables like '%undo%';+--------------------------+------------+| Variable_name | Value |+--------------------------+------------+| innodb_max_undo_log_size | 1073741824 || innodb_undo_directory | ./ || innodb_undo_log_truncate | OFF || innodb_undo_logs | 128 || innodb_undo_tablespaces | 0 |+--------------------------+------------+5 rows in set (0.02 sec)從提升MySQL效能的角度上來看,為了減少磁碟I/O的競爭,所以建議把回滾表空間和系統表空間分開存放,不讓回滾表空間和系統表空間共用同一個資料表空間檔案:ibdata1,可以使用引數innodb_undo_tablespaces引數配置回滾表空間的資料檔案的數目。配置引數在/etc/mysql/my.cnf配置檔案中如下:[mysqld] # 回滾表空間的配置 innodb_max_undo_log_size = 100M innodb_undo_log_truncate = ON innodb_undo_logs = 128 innodb_undo_tablespaces = 4 配置後的結果在MySQL的命令列中檢視如下:mysql> show variables like '%undo%'; +--------------------------+-----------+ | Variable_name | Value | +--------------------------+-----------+ | innodb_max_undo_log_size | 104857600 | | innodb_undo_directory | ./ | | innodb_undo_log_truncate | ON | | innodb_undo_logs | 128 | | innodb_undo_tablespaces | 4 | +--------------------------+-----------+ 5 rows in set (0.01 sec) 配置後可以檢視到對應的回滾表空間的資料檔案已經存在/var/lib/mysql/undo*,如下所示:root@test:/var/lib/mysql# ls -lstr undo* 10240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo002 10240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo001 10240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo004 10240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo003 root@test:/var/lib/mysql# 更多關於回滾表空間的問題參考:https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-tablespaces.html
從提升MySQL效能的角度上來看,為了減少磁碟I/O的競爭,所以建議把回滾表空間和系統表空間分開存放,不讓回滾表空間和系統表空間共用同一個資料表空間檔案:ibdata1,可以使用引數innodb_undo_tablespaces引數配置回滾表空間的資料檔案的數目。配置引數在/etc/mysql/my.cnf配置檔案中如下:[mysqld]# 回滾表空間的配置innodb_max_undo_log_size = 100Minnodb_undo_log_truncate = ONinnodb_undo_logs = 128innodb_undo_tablespaces = 4
配置後的結果在MySQL的命令列中檢視如下:
mysql> show variables like '%undo%';+--------------------------+-----------+| Variable_name | Value |+--------------------------+-----------+| innodb_max_undo_log_size | 104857600 || innodb_undo_directory | ./ || innodb_undo_log_truncate | ON || innodb_undo_logs | 128 || innodb_undo_tablespaces | 4 |+--------------------------+-----------+5 rows in set (0.01 sec)
配置後可以檢視到對應的回滾表空間的資料檔案已經存在/var/lib/mysql/undo*,如下所示:
root@test:/var/lib/mysql# ls -lstr undo*10240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo00210240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo00110240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo00410240 -rw-r----- 1 mysql mysql 10485760 Dec 28 15:45 undo003root@test:/var/lib/mysql#
更多關於回滾表空間的問題參考:https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-tablespaces.htmlRedo Log Tablespace:日誌表空間,對應到磁碟上面的資料檔案就是/var/lib/mysql/ib_logfile*,如下:root@test:/var/lib/mysql# ls -lstr ib_logfile*49152 -rw-r----- 1 mysql mysql 50331648 Dec 6 14:15 ib_logfile149152 -rw-r----- 1 mysql mysql 50331648 Dec 28 14:32 ib_logfile0root@test:/var/lib/mysql#
Temporary Tablespace:臨時表空間,對應到磁碟上面的資料檔案就是/var/lib/mysql/ibtmp1,如下:
root@test:/var/lib/mysql# ls -lstr ibtmp*12288 -rw-r----- 1 mysql mysql 12582912 Dec 28 14:32 ibtmp1root@test:/var/lib/mysql#
General Tablespace:一般表空間,就是平時我們用於儲存自己業務表中的資料用的表空間檔案。這裡需要注意的是目前很少使用這種以便的表空間了,因為它是多張表共用一個數據表空間檔案,如果資料量比較大的情況下經導致這個表空間資料檔案會很大,導致備份、遷移、恢復等動作都很困難。尤其是當其中某一個表的資料損壞而引起所有的表資料都不可訪問的情況。所以,推薦使用下面的獨立表空間檔案。File-Pre-Table Tablespace:它和上的General Tablespace的功能一樣,就是用來儲存我們的業務資料的表空間。但是它和上面的General Tablespace有一點不同,顧名思義,它是每一個表對應一個數據表空間檔案,這樣可以提高資料檔案併發時的磁碟I/O,同時可以避免因為資料表被損壞導致的所有資料表都不可用的情況。在恢復的時候,備份的時候,都很方便。該功能開啟的引數為:innodb_file_per_table=on。這也是目前MySQL5.7版本中預設的引數值。Segment段(Segment)由一個或多個區組成,區在檔案系統是一個連續分配的空間(在 InnoDB 中是連續的 64 個頁),不過在段中不要求區與區之間是相鄰的。段是資料庫中的分配單位,不同型別的資料庫物件以不同的段形式存在。
Table表和Segment段之間的關係如下:
表是邏輯概念,段是物理儲存概念。一張普通的表,對應一個段。一張表也可以有多個段,比如分割槽表,一個分割槽一個段。多張表也可以共享一個段,比如簇表,多個簇表共享一個段。通常情況下,建立一個表會建立一個段,但是:表的建立,並不意味著一定會建立一個段,比如臨時表的建立就不會建立段。建立其他的資料庫物件也會建立段,比如:檢視、索引對應著檢視段、索引段。Extent在 InnoDB 儲存引擎中,一個區塊分配 64 個連續的頁。因為 InnoDB 中的頁大小預設是 16KB,所以一個區的大小是 64*16KB=1MB。在任何情況下每個區大小都為1MB,為了保證頁的連續性,InnoDB儲存引擎每次從磁碟一次申請4-5個區。預設情況下,InnoDB儲存引擎的頁大小為16KB,即一個區中有64個連續的頁。
PagePage頁是InnoDB儲存引擎磁碟管理的最小單位,每個頁預設16KB:16384Byte = 16KB,可以使用如下命令在MySQL中進行檢視。
mysql> show variables like 'innodb_page_size';+------------------+-------+| Variable_name | Value |+------------------+-------+| innodb_page_size | 16384 |+------------------+-------+1 row in set (0.02 sec)
在MySQL5.6之前的版本,這個引數是不支援動態修改的,如果想要修改,只能自己修改原始碼編輯才可以。
而在5.6版本之後,引數innodb_page_size已經支援動態的配置,支援4KB、8KB、16KB(預設值)、32KB、64KB。但是這個配置也僅僅是在資料庫安裝好之後初始化之前自行配置,當有資料已經存在之後,這個引數是不能修改的。除非把資料透過mysqldump匯出來,重新初始化一個新的資料庫環境,然後修改引數之後,把匯出來的資料再次再匯入進去。
page頁再細粒度的劃分,可以分為如下幾種結構:
mysql innodb page structure.jpg
下面分別介紹一下page頁中各個組成部分的含義。
File Header:檔案頭資訊,比較重要的資訊有FIL_PAGE_PREV記錄上一個page頁和FIL_PAGE_NEXT下一個page頁的位置資訊,透過這兩個資訊,可以讓所有的page頁面組成一個雙向連結串列:page雙向連結串列.png
關於檔案頭File Header更為詳細的內容參考如下圖:
Page Header:記錄本頁儲存記錄的狀態資訊,比如本頁記錄數量,槽數量,詳細的資訊參考下圖:Infimun + Supermum Records:最小行與最大行記錄,是虛擬記錄,標記該page頁中,儲存的id最大的行和id最小的行記錄。具體可以參考如下圖的結構:page infimum and supremum.jpg
User Records:使用者真正的資料儲存區域,這裡真正存放使用者的行資料,它佔據了整個page頁的大部分空間。以單鏈表的形式儲存一條條行記錄。如下圖所示,他們在物理上不一定是有序的,可能剛開始是有序的,但是隨著增刪改的操作可能就無序了,但是在邏輯上是有序的:page內資料行儲存的方式.png
一個page頁中的多行記錄,再結合多個page頁,就形成如下的儲存結構:頁與頁直接是雙向連結串列,頁內的行記錄直接是單向連結串列。如下所示:page頁中的每一個箭頭可以理解為一行資料。page頁和頁之間的關係.png
基於上面的圖,當我們要查詢某一行記錄的時候,是透過下面的過程來查詢的。透過根節點開始遍歷一個索引的B+樹,透過各層非葉子節點達到底層的葉子節點的資料頁(Page),這個Page內部存放的都是葉子節點在Page內部從“Infimum”節點開始遍歷單鏈表(遍歷一般會被最佳化),如果找到鍵則返回。如果遍歷到了“Supremum”,說明當前Page裡沒有合適的鍵,這時藉助Page頁內部的next page指標,跳轉到下一個page繼續從“Infmum”開始逐個查詢。Free Space:存資料空間中尚未使用的區域,該頁中剩餘的空間,用於存放後續插入的資料。Page Directory:頁目錄,頁中某些記錄的相對位置,用於提升查詢效率。我們要在一個頁中查詢指定的一條記錄。除了從頭遍歷還有更高效率的方法麼?Page Directory提供瞭解決方案。InnoDB會將一個頁中的所有記錄劃分成若干個組,每組4-8個記錄。將每個組最後一個記錄相對於第一個記錄的地址偏移量(可以定位到真實資料記錄)提取出來存放在頁中一個叫做Page Directory的陣列中,陣列中的元素就是這些地址偏移量,也稱為槽(slot)。所以Page Directory就是由槽組成的。所以在一個頁中根據主鍵查詢記錄是很快的,步驟為:二分法確定該記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄。透過next_record屬性遍歷單鏈表找到記錄注意:二分法,適用於陣列。連結串列是順序存取,不是隨機存取,用二分查詢並不能提高查詢效率,因為你每次還得從第一個結點出發,找到指標LOW,HIGH,MIDDLE所指的元素,所以一般不在連結串列內使用二分查詢。File Trailer:檔案尾,刷盤時校驗頁是否完整。詳細內參考下圖:什麼是off-pageMySQL的表中儲存資料的時候,資料是一行一行的儲存的。這個行要落在innodb的最小儲存單位:page頁中。好比我們的書本中的一行一行的文字是在頁中,一個頁裡面有很多行。MySQL中的page頁,就是用來儲存多個行的基本單位。
但是如果一個行特別的大,大於了16KB的大小,那麼此時一個page頁,就容納不下這個行了,此時就要在用2個甚至更多的page頁來儲存這個行的資料,這種現象就是off-page,即行溢位,off-page是指一個表的單行的大小超過了MySQL預設的一個page頁的大小。一個行,要佔用多個頁來儲存對於這種現象,在不同的行儲存格式下面會有不同的處理方式,下面會有詳細的介紹。預設的方式是將多餘的資料需要在overflow-page溢位頁中儲存。
InnoDB的檔案儲存格式InnoDB儲存引擎有兩種檔案儲存格式:Antelope和Barracuda,而這兩種檔案儲存格式下,有分別支援兩種行儲存格式。
Antelope(羚羊):Compact(緊湊的)與Redundant(冗餘的)兩種行記錄格式compact:在儲存大的資料欄位的時候,比如blob、text型別的欄位,涉及到行溢位的問題。它在儲存text大欄位的時候,會在一個page頁中儲存前768個位元組,後面的位元組會儲存在溢位頁``overflow page`中。redundant:是最早的一種儲存格式,相比compact要佔用更多的儲存空間。現在級別已經廢棄。Barracuda(梭魚):Dynamic(動態的)和Compress(壓縮的)還支援compact、redundant兩種。dynamic:這種行儲存方式是目前MySQL5.7版本後預設的行儲存格式。它在儲存大欄位的時候,只會在page頁中儲存一個指向溢位頁的一個20個位元組的物理指標,而不會真正的去存放大欄位的內容。真正的欄位內容儲存在溢位頁overflow page中。這種方式,針對溢位列所在的新頁利用率更高,查詢的效率會減少磁碟的I/O互動次數,提高查效率。compress:相比dynamic,除了基本功能和dynamic一樣之外,它是把欄位內容以壓縮的方式儲存在page頁中,但是這種壓縮只是在物理儲存上的壓縮。在需要查詢對應的欄位內容的時候,需要從物理的page頁面中,讀取到記憶體中的資料需要進行相應的解壓縮的操作,這樣就需要大量的CPU的支援,降低的資料庫的TPS,影響資料庫的響應時間,這是一種以時間來換取空間的思想。而在當前磁碟儲存空間不是瓶頸的前提下,這種方式一般不被大家所認可了。因為磁碟價格也不貴,花費時間在CPU解壓資料上而換取節省磁碟空間的成本。這是一種得不償失的做法。注意:在Barracuda檔案儲存格式下,也是支援compact和redundant這兩種行儲存格式的,這個是為了將檔案儲存格式從Antelope向Barracuda慢慢過度才支援的。
檢視MySQL資料庫innodb儲存引擎使用的檔案格式和行儲存格式的命令如下:
mysql> show variables like 'innodb_file%';+--------------------------+-----------+| Variable_name | Value |+--------------------------+-----------+| innodb_file_format | Barracuda || innodb_file_format_check | ON || innodb_file_format_max | Barracuda || innodb_file_per_table | ON |+--------------------------+-----------+4 rows in set (0.01 sec)mysql> show variables like 'innodb_default_row_format';+---------------------------+---------+| Variable_name | Value |+---------------------------+---------+| innodb_default_row_format | dynamic |+---------------------------+---------+1 row in set (0.01 sec)