1.引言
在项目中会遇到各种各样的慢查询的问题,对于千万级的表,如果使用比较笨的查询方式,查询一条SQL可能需要几秒甚至几十秒,如果将索引设置的比较合理,可以将查询变得仍然非常快。
2.索引的本质
索引:帮助MySQL高效获取数据的排好序的数据结构。
索引的数据结构:
1.二叉树
2.红黑树
3.Hash表
4.B-Tree
如果表中没有索引,检索数据的时候就需要一行一行的去找,消耗的时间非常大。
每个数据在存储的时候,存储在磁盘上是,看起来是挨在一起的,但是其实不是挨在一起的,这个需要注意!!!
从磁盘中读取一次数据,被称之为磁盘IO,一次磁盘IO的性能是比较低的,如果我们对磁盘上的某一个数据进行查询的时候,顺序从表中查询数据,每次拿一个进行比对一次,这样整体来看是比较消耗性能的,整体的性能是不高的。
我们可以通过建立索引,将查询(磁盘IO)的次数控制在一定量内,就可以大大提升性能了。
为什么不选择使用搜索二叉树?
搜索二叉树并没有自平衡的功能,很有可能退化为链表结构。
为什么不选择使用平衡搜索二叉树?如红黑树,AVL树?
在平时生产环境使用数据库的时候,单表数据量常常会达到百万级,千万级,假设单表数据量为500万,即使使用红黑树,树的高度也会达到22,那么即使使用索引查询数据,也要可能进行22次磁盘IO操作,这个性能消耗对于MySQL来说也是比较大的。
为什么B-Tree更加合适?
1.B-Tree的特点:
叶节点具有相同的深度,叶节点的指针为空。
所有索引元素不重复。
节点中的数据索引从左到右递增排列。
2.B-Tree的数据结构:
每个节点都会存储多个key-value结构,key存储的是索引值,data存储的是索引所在行数据的硬盘地址。
为什么MySQL索引最终决定选择使用B+Tree(B-Tree的变种)?
1.B+Tree的特点:
非叶子节点不存粗data,只存储索引(做一个数据冗余),可以存放更多的索引数据。
叶子节点包含所有的索引字段。
叶子节点用指针连接,提高区间访问的性能。
2.B+Tree的数据结构:
MySQL中默认设定的一个节点可以存储的索引数据是16kb,可以使用SHOW GLOBAL STATUS like 'Innodb_page_size'查看“:
SHOW GLOBAL STATUS like 'Innodb_page_size';
这是MySQL设置的默认值,不建议去修改,这是MySQL官方进行了大量测试得出的结果,多方考量了各种情况。
现在举例展示定位数据的整体过程:
整个查询30这个索引对应的数据的过程花费的消耗是三次磁盘IO和一次内存IO,相对来说内存的速度是极快的,对于磁盘IO消耗的时间是可以忽略不计的,所以这次查询消耗也就是三次网络IO。
那为什么MySQL不直接干脆在一个节点上存储所有索引呢?这样只需要一次磁盘IO呀。
答案:在系统中,内存是有限的,生产环境下一个表存储的数据可能会达到百万级,千万级,一次磁盘IO将所有的索引数据和数据查询到内存中,数据量相对来说是巨大的,直接将内存干爆了,设计的不合理。而且这样不就又变成了顺序查找数据了吗?这样整体查询数据的速度也不会很快的。
MySQL选择16kb作为节点的存储空间,可以存储多少数据呢?
答案:这里以索引是bigint类型的作例子,bigint = 8byte,数据地址在MySQL底层C语言中是占用了6byte,16kb / (8 + 6)byte,一个节点即可以存储1170个元素。假设data的数据是1kb,一个叶节点也是可以存储16个元素的。
使用B+Tree对树高度进行削减的效果?
假设表中有2000万的数据,使用索引建立的B+Tree的高度也只有3而已,也就是说最多三次磁盘IO就可以找到相对应的数据了。
MySQL对于索引B+Tree节点常驻内存的操作。
在MySQL低版本中,会将根节点常驻到内存中,在MySQL高版本中,会将所有的非叶子节点进行常驻内存,也就是说对于千万级数据量的表来说,查询数据的时间成本消耗只有查询叶节点中数据这一次的磁盘IO而已,可见查询速度是相当快的。
面试题:对于B-Tree和B+Tree,为什么MySQL最终决定选择使用B+Tree?
影响树查询的关键是什么?是树的高度,树越高需要进行的磁盘IO的次数就越多,2000万级别的数据表的索引使用B+Tree进行构建出来的树的高度仅有3,但是如果使用的是B-Tree,由于B-Tree的特性,构建起来整个树的高度绝对是大于3的,而且也不能做内存常驻等优化手段。
3.不同存储引擎索引实现
首先先明确一个点:存储引擎的概念相对的是数据表,并不是相对的数据库,一个数据库中的多个数据表是可以分别使用不同的存储引擎的。
3.1MylSAM存储引擎
MySQL数据库中的数据都是存储在磁盘中的,如果数据表采用的是MyISAM存储引擎,数据表在硬盘中对应存储的是以下三个文件:
1.数据表名称.frm:数据表结构存储文件。
2.数据表名称.MYD:数据表数据存储文件。
3.数据表名称.MYI:数据表索引存储文件。
即MyISAM存储引擎,数据存储和索引存储文件是分离存储的。
在MYI索引文件中,存储的其实就是数据表构建的这一棵B+树,叶节点中的data数据是磁盘数据的地址,即当数据表采用的是MyISAM存储引擎的时候,通过索引去查询数据的时候,会根据B+Tree查询到数据的地址,然后再去MYD文件中查询真正的数据。
3.2InnoDB存储引擎
如果数据表采用的InnoDB存储引擎,数据表在硬盘中对应存储的是以下两个文件:
1.数据表名称.frm:数据表结构存储文件。
2.数据表名称.ibd:数据表索引+数据存储文件。
即InnoDB存储引擎,数据存储和索引存储文件是聚焦存储的。
在InnoDB存储引擎中,通过索引字段查询到的Data是数据表中的数据,即数据表中的其他数据是和索引字段存储在一起的,即存储在B+Tree的叶节点中。
3.3聚集索引和非聚集索引
非聚集索引:MyISAM存储引擎本身建立的索引就是非聚集索引,B+Tree叶节点的索引对应的Data是数据的地址,不是数据本身,即叶节点索引和数据是分开的,便是非聚集索引。
聚集索引:InnoDB存储引擎本身建立的索引就是聚集索引,B+Tree叶节点的索引对应的Data是数据本身,即叶节点索引和数据是黏合在一起的,便是聚集索引。
面试题:为什么建议InnoDB表必须建立主键,并且推荐使用整型的自增主键。
3.4面试题探究
3.4.1为什么InnoDB必须建立主键?
之所以InnoDB必须建立主键,这与以InnoDB为存储引擎的数据表密切相关,InnoDB引擎的数据表中数据和索引是附着在一起的,所以数据表中一定必须要有一个索引,一般情况下,我们都是使用主键作为索引进行搭建B+Tree数据存储结构。
如果发现数据表有主键,就会使用主键索引结合数据建立B+Tree结构存储相关数据。
如果数据表没有主键,就会去数据表中从前向后去寻找一个所有数据都不重复的一列数据,借助该列数据作为索引去构建B+Tree。
如果数据表没有主键,且没有从数据表中找到任何一列数据不一样,那么MySQL就会单独创建一个隐形的一列,去作为索引维护B+Tree。
虽然MySQL最后还是可以帮助我们建立起主键索引,但是不建议将这种工作要求给MySQL去完成。
3.4.2为什么索引建议使用整形数据列
前面提到过索引使用数据列中的数据建立的,并且每一层的整体元素都是以索引数据递增建立的索引节点,假设索引列中数据是整形,那么索引节点之间进行比较会非常简单,很容易就能比较成功,将整棵B+Tree建立起来。
但是如果索引数据不是整形数据,而是字符串数据之类的,比较大小就会比较耗费性能,整形数字容易比较,性能较高,所以建议索引建立走整形数据列。
3.4.3为什么索引建立使用自增整形数据列
前面已经介绍了为什么建议使用整形数据列,主要的目的就是为了提升索引树构建时的比较性能。
其实索引元素选择为自增的元素也是为了提高索引树构建的整体性能。
设定B+Tree中单节点设定为最多可以有四个元素,现在B+Tree中有七个元素,分别是1,2,3,4,5,6,8,那么建立起来的B+Tree就会如下所示:
但是如果现在不遵循自增原则,现在插入一个0007元素进去,会将元素插入到0006和0007之间:
但是现在最右侧的节点已经达到了最大元素数量,所以会进行分裂为两个节点,分裂为两个节点之后就会如下所示:
可以发现除了进行节点分裂操作以外,还进行了自平衡操作,整个过程的时间成本损耗是比较大的,所以建议建立索引的时候是整形自增的。
4.聚簇索引和非聚簇索引
在MySQL中索引被划分为两种,一种是聚簇索引另一种是非聚簇索引。
其中聚簇索引的意思是索引叶节点对应的数据是原始数据(即真数据),非聚簇索引的意思是索引叶节点对应的数据是数据的地址。
接下来就从存储引擎和索引类型两个方面去区分讲解。
4.1存储引擎
MyISAM存储引擎使用的索引是非聚簇索引,即索引B+Tree的叶节点对应的数据是数据的地址,不是真实的数据。
InnoDB存储引擎使用的一级索引(即主键索引)是聚簇索引,二级索引是非聚簇索引,下一步详细讲一下为什么要如此划分。
4.2索引类型
这里主要是介绍InnoDB存储引擎的索引类型。
索引类型分为主键索引和二级索引,主键索引顾名思义就是该字段既是主键又是索引,二级索引就是主键之外的字段建立的索引。
主键索引的B+Tree索引树叶节点对应的数据是真实数据,所以主键索引被称之为聚簇索引。
二级索引的B+Tree索引树叶节点对应的数据是数据的地址,所以二级索引被称之为非聚簇索引,二级索引之所以被设计为是非聚簇索引是因为为了达到节省空间的效果。
二级索引都需要进行一次回表操作,通过回表操作查询真实的数据。
5.Hash索引
InnoDB存储引擎的数据表,除了可以建立以B+Tree为存储数据结构的索引以外,还可以建立起以Hash为存储结构的索引。
Hash索引不支持范围查找但是如果只是进行等值/IN查找速度会非常快,可以在项目中灵活使用(IO查询次数邵),因为对索引的key进行一次查询就可以定位到数据。
Hash索引的索引对应的数据都是数据的地址,属于非聚簇索引。
6.联合索引(多字段索引)
6.1概念
KEY idx_name_age_position (name age position) USING BTREE
这条语句的意思是建立一个使用B+Tree结构构建的联合索引idx_name_age_position,该联合索引涉及到多个字段,分别是name,age,position字段。
构建出的B+Tree如下:
其中数据进行排序的时候,是先按照name排序,再按照age排序,再按照position进行排序。
如果联合索引建立的是联合主键索引,叶节点对应的数据就是主键索引,通过联合索引查询到数据后,就会先获取到主键索引数据,再去回表查询主键索引对应的真实数据。
如果是辅助索引,
6.2最左前缀原则
最左前缀原则保证了,联合索引只在SELECT查询语句使用时,有最左字段时才会生效使用,否则索引不会生效。
解释一下为什么不会生效,还是看刚刚建立的索引树:
查询语句1:SELECT * FROM table WHERE name = Bill AND age = 30
执行该SQL语句时,会先通过name定位到节点,然后查询到最左边的数据时,发现该数据是最后一个30,又由于数据组织是排好序的,所以就不会继续向下找了,该索引树起到了辅助快速查询的作用。
查询语句2:SELECT * FROM table WHERE age = 30。
如果是这样就无法进行正常定位了,因为无法age只能在确定的name里是小范围有序的,如果放到全局来看,age是乱序的,所以此时联合索引会失效,就不会走联合索引了。