什麼是inno_space? inno_space
是一個可以直接通路InnoDB 内部檔案的指令行工具, 可以列印出檔案的内部結構.
Jeremy Cole 用ruby 寫了一個類似的工具, 不過不支援MySQL 8.0, 并且ruby 編譯以及改動起來特别麻煩, 是以用cpp 重寫了一個. inno_space 做到不依賴任何外部檔案, 隻需要make, 就可以得到可執行檔案, 做到開箱即用.
inno_space 除了支援列印出檔案的具體結構之外, 同時還支援修複 corrupt page 功能, 如果遇到InnoDB 表檔案中的page 損壞, 執行個體無法啟動的情況, 如果損壞的隻是leaf page, inno_space 可以将corrupt page 跳過, 進而保證執行個體能夠啟動, 并且将絕大部分的資料找回.
inno_space 還提供分析表檔案中的資料情況, 是否有過多的free page, 進而給使用者建議是否需要執行 optimize table 等等
具體可以看代碼, 在github 上面開源:
https://github.com/baotiao/inno_space/commits/main接下來會4篇文章介紹InnoDB 主要的從檔案, page, index, record 在具體檔案裡面是如何分布的, 這裡大量引用了Jeremy Cole 裡面的圖檔和文章的内容.
同時介紹的過程會結合inno_space 工具直覺的列印出檔案的内部結構.
InnoDB 最後的資料都會落到檔案中.
整體而言InnoDB 裡面除了redo log 以外都使用統一的結構進行管理, 包括system tablespace(ibdata1), user tablespace(使用者表空間), undo log, temp tablespace. 這個結構我們統稱space file.
- InnoDB space file 也就是整個InnoDB 檔案系統的管理, 介紹.ibd 檔案的基礎結構. InnoDB space file
- InnoDB page management 具體的在InnoDB file space 這些16kb 大小的page 是如何管理的 Page management
- InnoDB Index page 上面講了這16kb 的page 如何管理, 那麼我們細看一下最常見的page 類型, Index Page 存的是使用者表空間的資料, 這些Index Page 是如何維護成一個table 的資料 Index page
- InnoDB record 是具體在InnoDB page 裡面, Mysql 裡面的record 是如何儲存在InnoDB page 裡面的 InnoDB record
這篇文章隻描述InnoDB file space, 接下來會有文章介紹InnoDB page management, InnoDB page, InnoDB record
1. InnoDB space file 基本結構
Page
在InnoDB 裡面, 16kb 大小的page 是最小的原子單元
其他的大小都是在page 之上, 是以有:
1 page = 16kB = 16384 bytes
1 extent = 64 pages = 1 MB
FSP_HDR page = 256 extents = 16384 pages = 256 MB

page 有最基礎的38位元組的 FIL Header, 8位元組的FIL Trailer
主要的内容包括:
- Checksum: 這個page 的checksum, 用來判斷page 是否有corrupt
- Page Number: Page Number 可以計算出在檔案上的偏移量, 一個page 是否初始化了, 也可以看這個page number 是否設定對了, 這個值其實是備援的, 根據file offset 可以算出來, 是以這個值是否正确, 就可以知道這個page 是否被初始化了
- Previous Page/Next Page: 這個隻有在Index page 的時候才有用, 而且隻有leaf page 的時候才有用, non-leaf page 是沒用的, 大部分類型的page 并沒有使用這個字段.
-
LSN for last page modification: 刷髒的時候, 寫入這個page 的 newest_modification_lsn
mach_write_to_8(page + FIL_PAGE_LSN, newest_lsn);
- Page Type: 這個page 具體的類型, 比如是btree index leaf-page, undo log page, btree index non-leaf page, insert buffer, fresh allocated page, 屬于ibdata1 的system page 等等. Page Type 最重要, 決定這個page 的用途類型, 裡面很多字段就不一樣了
-
Flush LSN: 儲存的是已經flush 到磁盤的page 的最大lsn 資訊. 隻有在space 0 page 0 這個page 裡面有用, 其他地方都沒用.. 什麼用途?什麼時候寫入? 什麼時候讀取?
在進行shutdown 的時候, 或者執行force checkpoint的時候通過 fil_write_flushed_lsn_to_data_files 寫入.
用途是在啟動的時候, 讀取這個flush lsn, 可以確定這個lsn 之前的page 已經刷到磁盤了, 從這個flush lsn 之後的redo log 才是uncheckpoint redo log, 但是其實redo log 裡面已經有了 checkpoint 的資訊了, 為何還需要這個字段?
logs_empty_and_mark_files_at_shutdown =>
在執行個體啟動的時候, innobase_start_or_create_for_mysql => open_or_create_data_files => fil_read_first_page
fil_read_first_page 裡面會讀取出這個lsn 資訊, 用于更新啟動的時候的 min_flushed_lsn, max_flushed_lsn. 因為這個時候redo log 子產品還沒有初始化, 可以拿這個兩個Lsn 做一些簡單的判斷
整體來看, 這個字段目前已經沒啥用了, 但是每一個page 都占用了8位元組的空間, 還是比較浪費, 可以充分複用
- Space ID: 目前Page 所屬space ID (8.0 裡面已經将該字段删除了)
通過inno_space 可以看到相應的結構:
./inno -f ~/git/primary/dbs2250/sbtest/sbtest1.ibd -p 10
==========================block==========================
FIL Header:
CheckSum: 2065869235
Page number: 10
Previous Page: 9
Next Page: 11
Page LSN: 554513658770
Page Type: 17855
Flush LSN: 0
Space file
一個space file 就是2^32 個page 的合集, 連續64個page 叫做extent, 256個連續的extent 會有一個XDES(extent descriptor) 進行管理, 第一個XDES 又叫做FSP_HDR, 還有一些額外的資訊.
下圖就是這個基本檔案組織結構的描述, 無論是undo space, system space, 使用者的table space 都是這樣結構
所有的space file 前3個page 都是一樣.
page 0 是 FSP_HDR(file space header)
page 1 是 insert buffer bitmap
page 2 是 inode page, 下一節會介紹
The system space
system space 的space id = 0, 檔案名叫 ibdata1, 也就是系統檔案.
page 0, 1, 2 這3個page 所有的space file 都一樣
在system space 裡面接下來的3, 4, 5 等等page 也都是有指定的用途
page 3 存放的是insert buffer 相關資訊
page 4 存放的是insert buffer tree 的root page
page 5 存放的是trx_sys 子產品相關資訊, 比如最新的trx id, binlog 資訊等等.
page 6 存放的是FSP_FIRST_RSEG_PAGE_NO, 也就是undo log rollback segment的header page. 其他的undo log rollback segment 都在不同的undo log 檔案中
page 7 存放的是 FSP_DICT_HDR_PAGE_NO, 存放的是DD 相關的資訊
page 64-127 是first 64 個double write buffer 的位置
page 128-191 是second 64個double write buffer 的位置
剩下的其他page 就有可能被申請成Undo log page 等等了
通過inno_space 打開 ibdata1檔案可以觀察到如下的資訊
File path /home/zongzhi.czz/git/primary/log2250/ibdata1 path
File size 209715200
start end count type
0 0 1 FSP HDR
1 1 1 INSERT BUFFER BITMAP
2 2 1 INDEX NODE PAGE
3 3 1 SYSTEM PAGE
4 4 1 INDEX PAGE
5 5 1 TRX SYSTEM PAGE
6 7 2 SYSTEM PAGE
8 8 1 SDI INDEX PAGE
9 12799 12790 FRESHLY ALLOCATED PAGE
打開一個普通的使用者表空間, 可以看到如下的結構.
└─[$] ./inno -f ~/git/primary/dbs2250/sbtest/sbtest1.ibd -c list-page-type
File path /home/zongzhi.czz/git/primary/dbs2250/sbtest/sbtest1.ibd path, page num 0
page num 0
==========================space page type==========================
File size 2604662784
start end count type
0 0 1 FSP HDR
1 1 1 INSERT BUFFER BITMAP
2 2 1 INDEX NODE PAGE
3 3 1 SDI INDEX PAGE
4 16383 16380 INDEX PAGE
16384 16384 1 XDES
16385 16385 1 INSERT BUFFER BITMAP
16386 31990 15605 INDEX PAGE
31991 31999 9 FRESHLY ALLOCATED PAGE
32000 32767 768 INDEX PAGE
32768 32768 1 XDES
32769 32769 1 INSERT BUFFER BITMAP
32770 49151 16382 INDEX PAGE
49152 49152 1 XDES
49153 49153 1 INSERT BUFFER BITMAP
49154 65535 16382 INDEX PAGE
65536 65536 1 XDES
65537 65537 1 INSERT BUFFER BITMAP
65538 81919 16382 INDEX PAGE
81920 81920 1 XDES
下一篇實體頁管理我們會更詳細的介紹.
File Per Table
InnoDB 常見的file per table 模式下. 一個table 對應一個.ibd 檔案.
page 3 一般是 primary index root page.
page 4 一般是 secondary index root page. 當然這裡是create table 就指定的時候, 比如如下 page 4 一般是k_1 這個index 的root page
Create Table: CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=237723 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
如果後面運作過程中再加的新的 secondary index, 新的Index的root page 那就不會是連續着的, 而是分散在其他page 上了
alter table sbtest1 add index idx_c(c);
比如執行alter table 以後, 額外增加的一個index, 通過inno_space 工具可以看到每一個index 的root page 所在等等
Example 2:
./inno -f ~/git/primary/dbs2250/sbtest/sbtest1.ibd -c index-summary
File path /home/zongzhi.czz/git/primary/dbs2250/sbtest/sbtest1.ibd path, page num 0
==========================Space Header==========================
Space ID: 15
Highest Page number: 158976
Free limit Page Number: 152256
FREE_FRAG page number: 24
Next Seg ID: 7
File size 2604662784
========Primary index========
Primary index root page space_id 15 page_no 4
Btree hight: 2
<<<Leaf page segment>>>
SEGMENT id 4, space id 15
Extents information:
FULL extent list size 2140
FREE extent list size 0
PARTIALLY FREE extent list size 1
Pages information:
Reserved page num: 137056
Used page num: 137003
Free page num: 53
<<<Non-Leaf page segment>>>
SEGMENT id 3, space id 15
Extents information:
FULL extent list size 1
FREE extent list size 0
PARTIALLY FREE extent list size 1
Pages information:
Reserved page num: 160
Used page num: 116
Free page num: 44
========Secondary index========
Secondary index root page space_id 15 page_no 31940
Btree hight: 2
<<<Leaf page segment>>>
SEGMENT id 6, space id 15
Extents information:
FULL extent list size 7
FREE extent list size 0
PARTIALLY FREE extent list size 219
Pages information:
Reserved page num: 14465
Used page num: 12160
Free page num: 2305
<<<Non-Leaf page segment>>>
SEGMENT id 5, space id 15
Extents information:
FULL extent list size 0
FREE extent list size 0
PARTIALLY FREE extent list size 0
Pages information:
Reserved page num: 19
Used page num: 19
Free page num: 0
**Suggestion**
File size 2604662784, reserved but not used space 39354368, percentage 1.51%
Optimize table will get new fie size 2565308416
- 這裡tablespace id 是15
- Btree 的高度是3層
- secondary Index 由于隻存索引, 是以primary index 占用的空間是secondary index 的10倍
- primary Index 上面大量的page 都是用滿的狀态, 而secondary 會20% 左右的空閑page
- 整體而言, 空閑page 隻占了檔案的1.51% 左右, 是以不需要做optimize table 操作的