在学习Innodb存储引擎的索引原理之前,需要先搞明白B+树,如果数据结构比较薄弱,需要从以下几个树开始。
在 InnoDB 中,表都是根据主键顺序以索引的形式存放的(如果表中没有主键呢? innodb会默认生成一个rowId作为唯一),这种存储方式的表称为索引组织表,InnoDB 使用了 B+ 树索引模型,所以数据都是存储在 B+ 树中的。
每一个索引在 InnoDB 里面对应一棵 B+ 树。
例子:
mysql> create table T(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;
id是主键索引,k是辅助索引,或者叫二级索引。
表中 R1~R5 的 (ID,k) 值分别为 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),两棵树的示例示意图如下:
主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)。
非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。
注意:
1.非叶子节点:页由N个键值+指针组成
2.叶子节点:主键索引的话,叶子节点存放的是数据 + 指针,辅助索引的话,叶子节点存放的是主键的值 + 指针
基于主键索引和普通索引的查询有什么区别?
还是基于上面的例子:
如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树;先从ID开始,500在 300 -700之间,拿到指针,直接读到叶子节点,300-600直接可以很快找到500,只需要两次IO,两次查找即可。
如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为回表。
计算公式:根节点的指针数量 * 单个叶子节点记录行数
单个叶子节点记录行数:InnoDB 中被称作页(page),大小是可以设的,默认为16KB。假设数据库中一行记录大小为1KB,即每个页中能存16行记录。
根节点的指针数量:还是以主键索引来看,假如说主键是bigint来算,长度8个字节,指针大小是6个字节,一共14个字节,16384(16Kb*1024) / 14 = 1170
数量:1170 * 16 = 18720 (2层高),1170 * 1170 * 16 = 21902400 (3层) mysql中一般单表不会存2千多万这么多的数据。
因此在数据库中,B+树的高度一般都在2~4层,这也就是说查找某一键值的行记录时最多只需要2到4次IO,这倒不错。因为当前一般的机械磁盘每秒至少可以做100次IO,2~4次的IO意味着查询时间只需0.02~0.04秒。
为什么说需要2-4次IO即可呢?
因为一次读取1个页(16kb),第一次先读取根,根据B+树的特点,可以得到指针,就是知道了下一次要读取的页是哪一个,如果层高是2-4次,只要根据指针,最多读取2-4次就行了。
聚集索引(clustered index)就是按照每张表的主键构造一棵B+树,同时叶子节点中存放的即为整张表的行记录数据,也将聚集索引的叶子节点称为数据页。同B+树数据结构一样,每个数据页都通过一个双向链表来进行链接。每张表只能拥有一个聚集索引。查询优化器倾向于采用聚集索引。因为聚集索引能够在B+树索引的叶子节点上直接找到数据。此外,由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值的查询。查询优化器能够快速发现某一段范围的数据页需要扫描。
对于辅助索引(Secondary Index,也称非聚集索引),叶子节点并不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含了一个书签(bookmark)。该书签用来告诉InnoDB存储引擎哪里可以找到与索引相对应的行数据。
当通过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并通过叶级别的指针获得指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录。举例来说,如果在一棵高度为3的辅助索引树中查找数据,那需要对这棵辅助索引树遍历3次找到指定主键,如果聚集索引树的高度同样为3,那么还需要对聚集索引树进行3次查找,最终找到一个完整的行数据所在的页,因此一共需要6次逻辑IO访问以得到最终的一个数据页。
当你的逻辑需求是查到所有名字是“张三”的人时,可以快速定位到 ID4,然后向后遍历得到所有需要的结果。
如果你要查的是所有名字第一个字是“张”的人,你的 SQL 语句的条件是"where name like ‘张 %’"。这时,你也能够用上这个索引,查找到第一个符合条件的记录是 ID3,然后向后遍历,直到不满足条件为止。
可以看到,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。
4.索引下推
举个例子:
以联合索引(name, age)为例。如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是 10 岁的所有男孩”。
mysql> select * from tuser where name like '张%' and age=10 and ismale=1;
根据最左匹配原则,可以命中联合索引,那么就会根据联合索引叶子节点内的值去一个个找对应的聚簇索引。这里就需要多次回表。
而 MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
根据例子来说,一个比较通俗的解释:对于联合索引来说,如果命中了一部分,另一部分需要去回表去查,在回表的查的时候,会首选过滤掉不合法的数据,避免回表多次。
参考:https://jaskey.github.io/blog/2016/01/19/mysql-bad-sql-with-no-index/
美团点评:https://tech.meituan.com/2014/06/30/mysql-index.html
1)完全理解数据库的索引,需要熟悉数据结构内树相关的详细知识。
2)内容参考极客时间《Mysql实战45讲》,《MySQL技术内幕》。