如果查询条件是主键的话,我们可以遍历页目录,使用二分法定位到数据,但如果我们的查询条件不是主键时,那么我们的页目录就用不上了,我们只能遍历所有的页,当数据量十分庞大的时候,查询效率十分低下。
最后一层存放用户记录,称为叶子节点;
除最后一层外的节点用于存放目录项,均称为非叶子节点
通常一颗B+树的层高不会超过4,原因见B+树的构建。
首先,我们每一个数据页中都会记录该数据页中用户记录最大主键值和最小主键值,如下:
基于数据页中的最大记录和最小记录,我们可以针对数据页形成一个目录:
上边的目录项就是数据页的目录,目录项中记录了对应数据页的最小值,而被目录项组织起来的数据页的主键值必须是依次递增了,也就是说目录项2中所有记录的主键值肯定是要大于目录项1中所有记录的主键值的。
之后,为了使数据页的目录更灵活,MySQL的设计者将这些目录信息存储到了“页”中,存储这些目录信息的页也被称为“索引页”,效果如下:
索引页中的记录有一个特点,就是它们头信息中的record_type为1,代表它们是目录项记录,而我们的普通记录对应的record_type是0。
当我们的数据页特别多的时候,我们需要用来记录页的目录项页就特别多了,那目录页多起来了后,我们为了提高检索效率,还得对目录项页整理目录,如下图:
那么到这里,我们的“树”已经呼之欲出了,这就是“B+”树的形成过程。
这里我们做一个简单计算,来说明为什么B+树不可能超过4层:
假设目录项页中可以记录的记录条数为1000,一个数据页中能记录的用户记录为100条,那4层B+树总共可以记录的信息有:100010001000*100=1000亿条。我们正常一张表是不可能记录这么多数据的。
上边描述的B+树我们称之为聚簇索引,因为叶子节点上存储了完整的用户记录,索引即记录。
我们上边描述的索引是基于主键生成的聚簇索引,我们在使用主键检索数据时,该索引效率很高,但是当我们使用非主键进行查找时,该索引就无法使用了,那么为了提高非主键查询时的查询效率,我们可以针对非主键字段建立索引,此类索引称为“二级索引”。
二级索引的特点:
针对多个列建立索引,假如我们对c1和c2列建立联合索引,我们会先根据c1进行排序,当c1值相同时,我们再对c2进行排序。效果图如下:
查询条件命中了二级索引,但是目标字段却不在二级索引中,此时就需要根据二级索引中记录的主键值回到聚簇索引中查询对应的记录值
回表是随机I/O,通常顺序I/O的性能是要由于随机I/O的,当我们回表次数过多的时候,innodb优化器会选择进行全表扫描
时间上的代价:每一次增删改的时候,我们都要维护对应的B+树
空间上的代价:每建立一个索引,都会产生一颗B+树
全值匹配:查询条件和列值完全匹配
最左匹配:假设我们针对列a,b,c建立了联合索引idx_a_b_c:
匹配列前缀:B+树中对于字符串进行了排序,所以我们使用部分前缀也可以快速地定位到数据,例如
1 | select column A from table where A like "As%" |
但是如果只给出中间或者末尾的某个字符,该索引就无法使用了,因为innodb并不会为某一列数据中间或末尾几个字符进行排序。
范围查询:当使用范围查询时,只有联合索引做左边的列能够使用到
精确匹配某一列并范围匹配另外一列:对于联合索引来说,如果可以精确匹配第一列,那么第二个条件可以范围匹配
用于排序:通常当我们的sql中包含order by的时候,我们的做法是将数据load到内存中,在内存中对数据进行排序,当我们where条件名字的数据过多时,甚至还可能要使用到磁盘空间来完成排序,那么一但和磁盘沾边,那我们的查询效率就会很低了。但是如果我们的ordey by中使用索引字段来进行排序的话,我们就可以直接从索引里提取数据,再进行一次回表就可以的。
不可以使用索引进行排序的情况:
用于分组:如果分组条件和我们索引字段一致的话,会提高分组sql语句的效率
覆盖索引:我们查询的目标列最好是索引中的列