我们知道InnoDB表是索引组织表,分为聚集索引和二级索引,但不管哪种索引,其结构都是B+树结构。由于InnoDB B+树结构高扇区特性,所以每个索引高度基本在3-4层之间,如果数据量比较大,页比较小,如4K,那么高度也是可能破4的。问题来了,以上说法都是我们根据理论知识计算得出的结果,那么在InnoDB中如何查看每个索引的高度呢?InnoDB也没有提供相应的视图进行查看。然,只要明白InnoDB索引的内部构造,就能迅速得出索引的高度。
姜总这篇文章给出了一个方法,如下图InnoDB索引的内部构造:
InnoDB是索引组织表,每个页都包含一个PAGE_LEVEL的信息(见上图右半部分),用于表示当前页所在索引中的高度。默认叶子节点的高度为0,那么root页的PAGE_LEVEL + 1就是这棵索引的高度。接下去的问题就是怎样得到一张表所有索引的Root页所在的位置呢?其实InnoDB中Root页默认就在3号页,且Root页的位置通常是不会更改的,为什么是3号页呢?因为0,1,2号的Page被类型为“FSP_HDR”、“IBUF_BITMAP”、“INODE”分别占用了,从3号Page开始才是存放数据和索引的页(Index)。
其实官方提供了内部视图来查看每个索引的Root页,通过下面的SQL语句可以查出某这表对应索引的Root页:
1 2 3 4 5 6 7 8 9 10 |
SELECT b.name, a.name, index_id, TYPE, a.space, a.PAGE_NO FROM information_schema.INNODB_SYS_INDEXES a, information_schema.INNODB_SYS_TABLES b WHERE a.table_id = b.table_id AND a.space <> 0; |
运行上述的SQL语句应该可以得到类似如下的结果(space等于0的为系统表):
1 2 3 4 5 6 7 |
+----------------+---------+----------+------+-------+---------+ | name | name | index_id | TYPE | space | PAGE_NO | +----------------+---------+----------+------+-------+---------+ | sbtest/sbtest1 | PRIMARY | 319 | 3 | 138 | 3 | | sbtest/sbtest1 | idx_k_c | 320 | 0 | 138 | 4 | +----------------+---------+----------+------+-------+---------+ 2 rows in set (0.00 sec) |
其中(SPACE,PAGE_NO)就是索引的Root页。SPACE对应每个表空间ID号,PAGE_NO对应每个页的ID号。
有了这些信息就可以方便的定位啦,因为PAGE_LEVEL在每个页的偏移量64位置初,占用两个字节,通过hexdump这样的工具就可以快速定位到所需要的树高度信息:
1 2 |
$ hexdump -s 49216 -n 02 /var/lib/mysql/sbtest/sbtest1.ibd 000c040 0200 |
查看sbtest1表,49216表示的是3*16384+64(这里innodb_page_size设置为了16384,如果是8192就是3*8192),即第3个页偏移量64位置开始读取2个字节,这里PAGE_LEVEL为00 02,那么索引的高度就为3。
最后,如果我们不知道索引内部结构,那么就可以使用工具来协助,innodb_ruby是一个非常好的各种维度查看索引及页结构的工具。所以,同样使用innodb_ruby查出每个表的索引信息。
1 2 3 4 5 6 |
$ innodb_space -s ibdata1 -T sbtest/sbtest1 space-indexes id name root fseg used allocated fill_factor 319 PRIMARY 3 internal 36 96 37.50% 319 PRIMARY 3 leaf 41094 41120 99.94% 320 idx_k_c 4 internal 521 608 85.69% 320 idx_k_c 4 leaf 36159 41376 87.39% |
id:表示此索引的ID。
name:索引的名称,PRIMARY代表的就是聚集索引,因为InnoDB表是聚集索引组织表,行记录就是聚集索引;idx_c就是辅助索引的名称。
root:索引中根节点的page号,可以看出聚集索引的Root节点是第3号page(前0、1、2号Page已经被使用),辅助索引的根节点是第4个page。
fseg:page的说明,internal表示非叶子节点或属于根节点,leaf表示叶子节点(也就是数据页)。
used:索引使用了多少个page,可以看出聚集索引的非叶子节点使用了36个page,叶子节点使用了41094个page;辅助索引的非叶子节点使用了521个页,叶子节点使用了36159个page。
allocated:索引分配了多少个page,可以看出聚集索引的非叶子节点分配了96个page,叶子节点分配了41120个page;辅助索引的非叶子节点分配了608个页,叶子节点分配了41376个page。
fill_factor:索引的填充度,used/allocated表示填充度,也就是实际使用的大小百分比。
同样,现在我们知道了Root节点页后,就可以使用innodb_ruby的另外一个功能,打印页结构信息,需要了解InnoDB页结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
$ innodb_space -s ibdata1 -T sbtest/sbtest1 -p 3 page-dump fil header: {:checksum=>900405545, :offset=>3, :prev=>nil, :next=>nil, :lsn=>20030130167, :type=>:INDEX, :flush_lsn=>0, :space_id=>138} fil trailer: {:checksum=>3300141601, :lsn_low32=>2850260983} page header: {:n_dir_slots=>9, :heap_top=>575, :garbage_offset=>0, :garbage_size=>0, :last_insert_offset=>567, :direction=>:right, :n_direction=>34, :n_recs=>35, :max_trx_id=>0, :level=>2, :index_id=>319, :n_heap=>37, :format=>:compact} fseg header: {:leaf=> <Innodb::Inode space=<Innodb::Space file="sbtest/sbtest1.ibd", page_size=16384, pages=94208>, fseg=2>, :internal=> <Innodb::Inode space=<Innodb::Space file="sbtest/sbtest1.ibd", page_size=16384, pages=94208>, fseg=1>} sizes: header 120 trailer 8 directory 18 free 15783 used 601 record 455 per record 13.00 page directory: [99, 164, 216, 268, 320, 372, 424, 476, 112] system records: ............................. |
页结构信息中有一个level字段,表示的就是Root节点页的高度,同样,level + 1就等于这个索引的高度。