一:
简化几个概念:
h:统称索引的高度;
h1:聚簇索引的高度;
h2:二级辅助索引的高度;
k:中间结点的扇出系数。
二:索引结构
叶子节点其实是双向链表,而叶子节点内的行数据是单向链表,该图未体现。
磁盘块其实是页,用操作系统中的术语来表达而已。
InnoDB中使用的是B+树聚集索引,主键索引叶子节点有整行的数据,辅助索引有主键值(用于回表查询)和索引值。
2.1 页的概念
Mysql的InnoDB是以页为存储单位的,每个B+Tree的节点都是一个页的大小,默认一页的大小是16K(与操作系统数据读取相关)。
>
数据页(即有行数据的叶子节点)
2.2 索引高度h与页面I/O数的关系
每次查询都要访问到叶子结点,其访问的页面数正好就是索引的高度h。例如,一次主键上的点查询SELECT * FROM USER WHERE id=1,那么要查询h1个页面才能找到叶子结点里的行数据,也即进行h1次页面I/O。(另外,二级索引基本都加载在内存里了,这里我们暂忽略这种情况。)
综上,查询对应的页面I/O数跟利用的索引有关,主要分为以下几种情况:
- 点查询:
- 聚族索引:h1
- 二级索引:
- 覆盖索引:h2
- 回表查询:h2+h1
- 范围查询:这种情况相对比较复杂,但跟点查询的原理类似,读者可自行分析;
- 全表查询:B+树的叶子结点是通过链表连接起来的,对于全表查询,需要从头到尾将所有的叶子结点访问一遍。
2.3 索引高度理论计算
索引页(非叶子节点)中可以分割为多个扇区,每个扇区再指向某子节点(某页)。
假设非叶子节点扇区数为k个、高度h、叶子结点的行记录数为n,则叶子结点数为k(h-1),总记录数为k(h-1)*n。
InnoDB每个页面默认16KB,假设主键是4B的int类型。对于非叶子节点,每个主键值后有个页号4B,还有6B的其他数据(参考《MySQL技术内幕:InnoDB存储引擎》),那么扇区个数k=16KB/(4B+4B+6B)≈1170。
假设每行记录大小为1KB,则每个叶子结点可以容纳的记录数n=16KB/1KB=16。
在高度h=3时,叶子结点数=1170^2 ≈137W,总记录数=1170^2*16=2190W!!也就是说,InnoDB通过三次索引页面的I/O,即可索引2190W行记录。
同理,在高度h=4时,总行数=1170^3*16≈256亿条!
三、动手查看索引真实高度
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;