索引是MySQL面试问题的重难点,在平时工作工作中也经常能接触到,最常见的sql优化手段就是在经常条件查询的字段上加索引。那么对索引究竟了解多少呢,什么聚簇索引?回表、索引覆盖又是什么意思?B+树又是怎样的数据结构?通过本文你可以了解到这些知识点。
基本概念
索引是什么
MySQL官方对索引的定义:索引(index)是帮助MySQL高效获取数据的数据结构。通过这句话,我们知道了索引的本质是一种数据结构,能够帮助提升查询数据的效率。抽象的不好理解,不妨可以类比为书籍的目录页,或者是字典,帮助快速定位查找的工具。
索引怎么用
索引作为数据库的一个重要功能,其语法也基本满足sql的创建、删除命令。
创建
create index idx_XXX on table_name(column_name(length))
如果字段是BLOB、TEXT类型,必须指定length。
或者使用
alter table table_name add index idx_name(column_name)
删除
drop index idx_name on table_name
查看
show index from table_name
索引分类
平时我们说的B+树索引、哈希索引、聚簇索引还有主键索引,可能从字面理解起来比较困难,觉得种类繁多而且纷繁复杂,其实是缺少归纳,好好分类就能梳理清楚各个名词的意思。
数据结构角度
- B-Tree : B树索引(它就是B树,而不是"B减树"!)
- B+Tree : B+树索引
- Hash索引
- Full-Text全文索引
物理存储角度
- 聚集索引 :表中行的物理顺序和索引中行的物理顺序相同
- 非聚集索引
逻辑角度
- 主键索引: 一种特殊的索引,不允许有空值
- 单列索引: 每个索引只包含单个列,一张表可以有多个
- 多列索引: 也叫联合索引、复合索引,在多个字段上创建的索引,遵循最左匹配原则(在查询条件中命中了联合索引的第一个字段,才会走索引)
- 空间索引: 对空间数据类型的字段创建的索引,是MyISAM的一种特殊索引类型,只作用于GEOMETRY、POINT、LINESTRING、POLYGON等空间数据类型上,主要用于地理空间数据场景。
存储方式角度
- 聚簇索引: InnoDB引擎结构的叶子结点的数据域,存放的就是实际的数据记录。即该引擎的数据文件本身就是主键索引文件。主键索引就是聚簇索引,因此一个表只有一个聚簇索引。
- 非聚簇索引: MyISAM引擎的索引文件和数据文件是分离的,该引擎索引结构的叶子结点的数据域,存放的并不是时间的数据记录,而是数据记录的地址。
B+树
理念先行
索引是在存储引擎层面实现的数据结构,不是所有的存储引擎都支持所有的索引类型。即使多个存储引擎支持同一个索引类型,具体的实现方式也可能有所区别。
MyISAM和InnoDB都是用B+树的数据结构,是在B-Tree的基础上进行改造,将所有的数据都存放在叶子结点上,并且叶子结点之间通过指针连接,形成类似链表的结构,提升了相邻数据的检索效率。
因此,我们要先了解下B-Tree了。
B-Tree
从名称中可以得到,它首先是一种树结构。哪种树?想一想,在快速查找这件事上,哪种树的效率最高,不难想出是红黑树。那么B-tree是红黑树的变种或者升级版本吗,并不是。它甚至不是平衡二叉树(红黑树是一种特殊的平衡二叉树),它只是一种“平衡查找树”。
再想一想,有查找效率更好的数据结构为什么不用,而要选择“中庸”呢。难道老外在设计上参考了中庸之道的文化?
上面所说的查找效率更好有一个前提条件,就是在内存里运行的话,平衡二叉树确实是更好的。但是设计MySQL索引的数据结构还要考虑一点:磁盘IO。
前置知识。InnoDB有页(page)这个概念,页是磁盘管理的最小单位。可通过参数innodb_page_size设置页的大小,一般为4k的整数倍,默认大小为16k.
系统从磁盘读取数据到内存是一磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要啥在磁盘里单取它。 而一个磁盘块的存储空间往往没有那么大,InnoDB每次申请磁盘空间时都会是若干地址连续的磁盘块以达到16KB,也就是页的大小。总结一下,就是让磁盘块的大小正好等于页的大小,实现一种“整取整读”的效果。
InnoDB把磁盘数据读入到内存时会以页为单位,在查询数据是如果一个页中的每条数据都能有助于定位数据记录的位置,这样就能够实现少从磁盘读几回,也能查找对应的数据,即减少了磁盘IO次数,提高IO层面的查询效率。
是时候引出B-Tree了,这种结构的数据可以让系统高效地找到数据所在的磁盘块。举个例子来讲,定义一个二元组【id,data】,id对应表里主键值,data为一行主键外的数据。
上图为一个3阶的B-Tree,每个节点占用一个盘快的磁盘空间,一个节点上有两个升序排列的id和三个指向子树根节点的指针,指针存储的是子节点所在的磁盘块地址。两个id分成的三个范围域对应三个指针指向的子树的数据的范围域。
模拟查找id=29的过程:
- 根据根节点找到磁盘块1,读入内存。磁盘IO记1次。
- 比较id29在区间(17,35),找到磁盘块1的指针p2。
- 根据p2指针找到磁盘块3,读入内存。磁盘IO记2次。
- 比较id29在区间(26,30),找到磁盘块3的指针p2.
- 根据p2指针找到磁盘块8,读入内存。磁盘IO记3次。
- 在磁盘块8中找到id=29的数据行。
分析以上过程,总计需要3次IO操作,和3次内存查找操作。由于内存中的id是一个有序的表结构,可以利用二分法查找提高效率。而3次磁盘IO是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘IO取到内存的数据都发挥了作用,从而提高了查询效率。
B+Tree
从上一节中的B-Tree结构可以看到每个节点不仅包含数据的id值,还有data。而每一个页的存储空间是有限的,如果data数据较大是将会导致每个节点能存储的id值很小,当存储的数据量很大同样会导致B-Tree的深度很大,增加IO次数,降低查询效率。
B+Tree是在B-Tree基础上的一种优化,所有数据记录节点都是按照id键值大小顺序存放在同一层的叶子结点上,非叶子结点只存储id值信息,这样可以加大每个节点存储的id数量,降低树的高度,“把树压扁”。同时增加了叶子节点间的指针,增加查询效率。
通过上图可以看到B+Tree区别于B-Tree的几个特点:
- 非叶子结点只存储键值信息
- 数据记录都存放在叶子结点中
- 叶子结点之间有一个双向指针
最左匹配
当B+Tree的data项是复合的数据结构,比如(name,age,sex)这种联合索引的时候,B+Tree是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,B+Tree会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,B+Tree就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
索引覆盖
了解索引覆盖先了解下回表,即查找的数据需要两次查询:第一次在索引里找到数据的主键,再通过主键查到对应的数据行。 索引覆盖就是指不需要回表的操作。使用explain关键字分析SQL,通过输出的extra列来判断,对于一个索引覆盖查询,显示为using index,MySQL查询优化器在执行查询前会决定是否有索引覆盖查询
索引使用场景
- 主键自动生成索引
- 查询条件中高频出现的字段
- 多表联查,外键关系建立索引
- 查询中的排序字段
- 查询中的统计或分组字段
但是,索引并不是越多越好。索引作为一种数据结构存放数据,也占用了空间。它提高了查询的效率,反作用就是降低了更新/插入表的速度,因为更新表时,MySQL不仅要保存数据,还要更新索引信息。
不建议建索引的情况
- 表的记录太少,基本上走全表也耗费不了啥时间的
- 经常增删改的表
- 数据重复且分布均匀的表字段
- 表里面高频更新的字段
- 根本没有出现在where条件中的字段