MySQL索引做为数据库优化的经常使用手段之一在项目优化中常常会被用到, 可是如何创建高效索引,有效的使用索引以及索引优化的背后究竟是什么原理?此次咱们深刻数据库索引,从索引的数据结构开始提及.node
索引为何能提升查询效率?当咱们有一个索引index(a)
以后,写一个查询语句where a = 4
.索引是怎么工做的.在学数据结构的时候学过红黑树,这是现现在使用的最普遍了的数据结构之一了,缘由就在于它查询高效.mysql
上图就是一颗红黑树.它有一个基本特性 :sql
某个节点的左子树的值必然都小于当前节点的值数据库
某个节点的右子树的值必然都大于当前节点的值数据结构
因此当咱们想要找值为6的节点的时候,即相似:where node=6
.它从节点13开始,性能
只要查询的值小于当前节点就顺着左子数查找优化
要查询的节点大于当前节点就顺着当前节点的右子树查找.spa
因此只要通过4次的查找(13,8,1,6)就能够找到6.因为红黑树的神奇特性,因此在查找的时候能够在O(log n)的时间内作查找操做.如今咱们考虑一下,若是咱们索引使用了红黑树以后,它怎么帮助咱们提升查询效率.指针
首先,每一个节点必然是一个结构体,包含以下属性:code
node { node *left, //指向其左子树的指针 node *right, //指向其右子树的指针 Point *p //指向表中某一行的指针. int data // 当前节点的值 }
当咱们对某个创建一个索引的时候,MySQL就会把这个字段的全部值创建起一个红黑树,红黑树中的每一个节点会有一个指针,这个指针指向当前数据的地址,好比A21是13所在行的指针,那么红黑树中值为13的节点就有一个指针指向这一行.
如今咱们来模拟一遍数据库查找的流程:SQL语句为select * from table_name where a = 25
,而且a字段创建了索引,那么查询的时候就不用遍历表了,只要查询红黑树,只要通过三次查找(13,17,25)就能够获得a=25所在行的指针.有了指针就能够读取到一整行的数据.
以上就是索引优化的原理.可是通常状况下,数据库中的数据是存放在磁盘中,读取磁盘中的数据要考虑到磁臂移动花费的时间给查询带来的影响,因此数据库中使用的索引通常不是红黑树,而是B树.虽说采用的数据结构不同,可是其原理都是一致的,因此即便是B树,咱们仍然能够按照上面的方式来理解索引查询优化原理.
MyISAM和InnoDB是MySQL经常使用的两个存储引擎,两个也都是采用B树做为索引的数据结构,可是虽然如此,二者仍是有很大区别的.
MyISAM索引中的指针就是指向数据所在地址
当有两个索引的时候,就分别有两个B树,两个索引的指针都是指向数据所在地址
MyISAM的这种索引方式被称为非汇集索引.而在InnoDB中,索引结构跟MyISAM有比较大的区别.
InnoDB中,整个数据表就是按照B树来存储着,整个表就是一颗巨大的B树.整个b树是按照表的主键索引的,因此通常InnoDB必需要求表有一个主键,若是没有的话,InnoDB会隐式的生成一个6个字节的整数型做为索引.
若是创建了其余索引的话,那么其索引的指针不是像MyISAM保存指向数据所在地址,而是保存主键值.这意味着InnoDB在使用非主键索引的时候须要遍历两次索引,一次遍历索引找到主键值,根据主键再次遍历主键索引找到数据行.
InnoDB中每一个节点的数据相似:
node { node *left //左子树指针 node *right //右子树指针 int index //索引值 Data data //index所在行的一整行数据 }
根据InnoDB索引的特殊性.因此能够得出一些使用InnoDB存储引擎的注意方式:
表最好要有一个主键.避免MySQL为咱们隐式生成一个主键.
主键除了确保惟一性,并且占用的空间要少,由于非主键索引都是保存主键值的,若是主键为相似身份证这类数据,那么会致使索引过大.
主键最好有auto_increment,否则再每次插入非单调数据的时候为了保持B树的结构会频繁分裂树结构致使性能下降
要想高效使用索引,就得知道什么样的查询语句会使用到索引.对于单列索引,只要where字句包含了索引就可使用到索引.可是不少人很容易忽略联合索引,觉得联合索引只是单纯的几个单列索引叠加在一块儿.其实否则,若是可以创建优秀的联合索引,效率会比创建多个单列索引好上不少.这里咱们使用MySQL的Explain命令来分析SQL执行的过程,因此在深刻索引以前来看看Explain怎么使用
使用Explain只要在查询语句前面加上explain就能够了.
mysql> explain select * from t1 where a=1 and b=2 and c=3; +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 15 | const,const,const | 1 | NULL | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ 1 row in set (0.00 sec)
如上 .执行Explain以后会返回给咱们相似上面的数据.这里挑几个咱们下面重点用到的字段.注意,有些没有提到的字段并非不重要,只是咱们重点在于使用这些字段分析索引.因此能够略过一些字段.
当值包含using index
的时候说明使用了覆盖索引.什么是覆盖索引,即select要查询的字段正好就是当作索引的字段.好比上面当查询为select a,b,c from t1 where a=1 and b=2 and c=3
.而咱们正好有一个索引idx_abc(a,b,c)
.因此这个查询就使用到了覆盖索引.使用覆盖索引有什么好处?回忆一下上面索引的原理,查询的时候查询到了B树的某一个节点,要获得这个节点中保存的主键的值,而后再次遍历主键索引才能查询到数据.若是咱们要查询的数据就是索引字段,那么就避免了第二次查找索引.
当值包含Using filesort
说明MySQL在查询完要的数据以后,还要对数据进行一次排序才能返回结果.
表示当前查询使用到索引的字节数.经过这个字段咱们能够分析出在查询的时候是否有效的使用了联合索引.那么如何计算ken_len?,这里有几个能够遵循的法则:
key_len 等于索引列类型字节长度,如int占据4个字节.
若是索引列为变长类型的数据须要 +2 字节,这个变长类型包括varchar,以及text等长数据创建的部分索引.
若是索引列容许为空,那么须要 +1 字节
字符类型的索引长度跟字符集有关.
根据上面的法则,若是有一个索引a,其字段定义为a varchar(10)
,而且a保存的是utf-8的数据,那么:
varchar为变长数据 须要 +2 字节
字段没有定义not null
,说明容许为空, 须要 +1 字节
保存的是utf-8数据,一个字符占据3字节, 须要 10*3 字节
因此,这个索引的长度为:2 + 1 + 10*3=33字节
key字段表示当前搜索使用到的索引
possible_能够表示搜索以前MySQL估计可能使用到的索引
这个字段说明了MySQL是如何查找表中的行.这个字段有以下的可能值,这些值从上到下依次代表了本次查询从最差到最优.
ALL : 表示MySQL必须扫描整张表才能找到所须要的行.注意这里的扫描是指从表的第一行数据开始扫描.
index : 跟ALL的同样 须要扫描全表才能找到须要的行,可是不一样的是扫描的顺序不是从表的第一行开始,而是根据索引的顺序扫描的.这个有一点好处是避免了排序,由于索引自己已是有序的.
range : 这种查询跟index不一样的地方在于没必要进行全表扫描这种类型的查询通常在where字句中带有<, between
的时候出现.因此这种状况下,只要扫描部分索引就能够了.
ref : 这种查找通常在使用复合索引的时候会出现,具体含义在下文给出.
除了这些以外, 还有另外的eq_ref,const,system,null
等可能.因为下文没有出现这些可能性,因此就不一一说明了.
好了,懂得根据Explain分析SQL执行过程以后,咱们来看看如何使用复合索引才是最高效的.
CREATE TABLE `t1` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL, KEY `idx_abc` (`a`,`b`,`c`) ) ENGINE=InnoDB DEFAULT CHARSET=utf-8
有上面的表结构,咱们创建了一个复合索引idx_abc
,其实至关于创建了三个索引:idx(a),idx(a,b),idx(a,b,c)
三个索引.可是在具体查询的时候,是否可以用上这些索引还须要深刻分析.
mysql> explain select * from t1 where a=1 and b=2 and c=3; +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 15 | const,const,const | 1 | NULL | +----+-------------+-------+------+---------------+---------+---------+-------------------+------+-------+ 1 row in set (0.00 sec)
当查询精确匹配索引中的每个字段的时候,就会使用到(a,b,c)索引.因此这里的key_len等于15,即 4+1+4+1+4+1=15.这里所谓的精确匹配搜索指的是查询类型为 = 或者 in
的时候.
并且这里要注意一点,MySQL对索引的顺序是敏感的,即定义索引的时候顺序为idx_abc(a,b,c)
,那么查询时候字句where的出现顺序也应该是a,b,c才能够用到索引.可是因为MySQL优化器会在查询以前帮咱们调整顺序.因此,即便你查询写成select * from t1 where b=1 and a=2 and c=3;
仍然可使用到索引.
mysql> explain select * from t1 where a =2 and c=4; +----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 5 | const | 1 | Using where; Using index | +----+-------------+-------+------+---------------+---------+---------+-------+------+--------------------------+ 1 row in set (0.00 sec)
这里where字句少了b字段,发如今查询时候只能用到索引a,这里就引出了一个最左前缀原理的概念.若是在查询的时候只是包含了索引定义顺序的某些字段,那么就只能用到复合索引的一部分.好比上面的查询,索引的定义顺序为(a,b,c).可是在查询的时候少了b字段,虽然Explain的key字段说明了使用到idx_abc
索引,可是从ken_len
中发现只是使用到了索引的第一列前缀.即只使用到了a.
当查询为:
mysql> explain select * from t1 where a=4 and b=4; +----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 10 | const,const | 1 | NULL | +----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+
这个时候(a,b)是按照索引定义顺序出现的,因此这里能够用到(a,b)索引.即key_len=10
根据上面说的,若是查询的时候没有按照索引定义的顺序来使用索引,那么只有左边的部分可使用到索引.如今咱们来看一个查询.
mysql> explain select * from t1 where b=4; +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+ | 1 | SIMPLE | t1 | index | NULL | idx_abc | 15 | NULL | 1 | Using where; Using index | +----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+ 1 row in set (0.00 sec)
这个查询只使用到了b,并无使用a.按照上面的最左前缀原理,应该是没办法使用索引才对,可是这里的 ken_len
为15说明使用到了索引.这个时候咱们须要看type字段,发现这个查询type=index
.而上一个为type=ref
.这两个有什么区别?
type=index
在解释Explain的使用的时候有讲到这种查询MySQL会对该索引进行扫描.这种只要where字句后面的字段为索引,或者复合索引的一部分,那么MySQL就会以index的方式进行扫描.可是这种扫描是很耗费性能的,由于他要从索引的第一个值开始扫描整张表.并且这种扫描是随机IO,由于这种扫描不是按照表的顺序扫描,而是按照索引的顺序扫描.这种查询的好处在于避免了排序.因此这里ken_len表示的不是使用索引进行查找数据,而是根据索引顺序进行扫描整张表
type=ref
,这种查找就是咱们日常说的使用索引能提升查询效率的查找,他是查找和扫描的混合体.这样讲可能有点难以理解.上栗子.
有索引idx_ac(a,b,c)
,那么索引在MySQL内部的构造相似上图,这有点相似 order by a b c
的感受,A字段是绝对有序的,只有在A字段同样的状况下,才对B进行排序.最后才是C.
当咱们查询where a=2 and c=5
的时候,因为a是绝对有序的,因此查询时候经过索引能够立刻查询到2,3行.以后c=5
因为没有了中间的B,因此C至关因而无序的.这个时候,MySQL只能扫描2,3行来找到所须要的数据.这样子,咱们也就解释了为何查询where a=3 and c=5
的时候只能用到索引a了.可是这个查询的的确确使用到了索引来加快查询速度.说着这个查询的type=ref
.而不是type=index
如今,咱们对上面的表增长一个字段alter table t1 add other int
.这样子,表的结构就变成
CREATE TABLE `t1` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL, `other` int(11) DEFAULT NULL, KEY `idx_abc` (`a`,`b`,`c`) )
这个时候咱们再执行查询语句
mysql> explain select * from t1 where c=4; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 1 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
发现,这里type=ALL
,按照上面Explain提到的,这个说明MySQL会去扫描全表,这是效率最低的状况.但是和上面的查询相比, 为何咱们查询的语句同样,查询的方式却从type=index
变成了type=all
?问题就在于咱们添加了一个字段,添加一个字段以后让select * from t1 where c=4
变成非覆盖索引的查询.在非覆盖索引查询的时候,没有遵循最左前缀原则的查询,只能扫描全表查询.
mysql> explain select * from t1 where a=4 and b<5 and c=4; +----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+ | 1 | SIMPLE | t1 | range | idx_abc | idx_abc | 10 | NULL | 1 | Using index condition | +----+-------------+-------+-------+---------------+---------+---------+------+------+-----------------------+ 1 row in set (0.00 sec)
上面这种查询,虽然知足最左前缀,可是中间的B为范围查询(<, between),那么范围查询以后的字段是用不到索引的,因此能够看出.这里的ken_len
为10.其原理跟上面的同样, 因为B是范围查询,因此至关于C是无序的.这个时候只能使用扫描的方式来查找C.
索引在排序中的做用仍是跟上面的同样.咱们经过一个例子来分析下.
表结构以下
CREATE TABLE `t1` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, `c` int(11) DEFAULT NULL, `other` int(11) DEFAULT NULL, KEY `idx_abc` (`a`,`b`,`c`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1
查询语句为:
mysql> explain select * from t1 where a=1 order by b; +----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | t1 | ref | idx_abc | idx_abc | 5 | const | 1 | Using where | +----+-------------+-------+------+---------------+---------+---------+-------+------+-------------+ 1 row in set (0.00 sec)
这里,咱们where字句只有a=1
一个,因此key_len=5
.可是Extra并无出现using filesort
的字段,说明这里没有进行排序.缘由跟上面的相似:MySQL经过索引 查找到a=1
的行数以后,因为a字段都相等,那么B字段已是有序的,因此就没必要再进行排序了.
若是查询换成:
mysql> explain select * from t1 where a=1 and b <3 order by c; +----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+ | 1 | SIMPLE | t1 | range | idx_abc | idx_abc | 10 | NULL | 1 | Using index condition; Using filesort | +----+-------------+-------+-------+---------------+---------+---------+------+------+---------------------------------------+ 1 row in set (0.00 sec)
这个时候在extra字段多了Using filesort
,说明MySQL查询到所须要的字段以后还要进行排序,由于这个SQL语句B是范围查询,因此C是无序的.