当提到MySQL数据库的时候,咱们的脑海里会想起几个关键字:索引、事务、数据库锁等等,索引是MySQL的灵魂,是平时进行查询时的利器,也是面试中的重中之重。程序员
可能你了解索引的底层是b+树,会加快查询,也会在表中创建索引,但这是远远不够的,这里列举几个索引常见的面试题:面试
一、索引为何要用b+树这种数据结构?算法
二、汇集索引和非汇集索引的区别?数据库
三、索引何时会失效,最左匹配原则是什么?segmentfault
当遇到这些问题的时候,可能会发现本身对索引仍是只知其一;不知其二,今天咱们一块儿学习MySQL的索引。缓存
首先来看在MySQL数据库中,一条查询语句是如何执行的,索引出如今哪一个环节,起到了什么做用。服务器
当执行SQL语句时,应用程序会链接到相应的数据库服务器,而后服务器对SQL进行处理。数据结构
接着数据库服务器会先去查询是否有该SQL语句的缓存,key是查询的语句,value是查询的结果。若是你的查询可以直接命中,就会直接从缓存中拿出value来返回客户端。函数
注:查询不会被解析、不会生成执行计划、不会被执行。性能
若是没有命中缓存,则开始第三步。
最后,数据库服务器将查询结果返回给客户端。(若是查询能够缓存,MySQL也会将结果放到查询缓存中)
这就是一条查询语句的执行流程,能够看到索引出如今优化SQL的流程步骤中,接下来了解索引究竟是什么?
先简单地了解一下索引的基本概念。
索引是帮助数据库高效获取数据的数据结构。
汇集索引表记录的排列顺序和索引的排列顺序一致,因此查询效率快,由于只要找到第一个索引值记录,其他的连续性的记录在物理表中也会连续存放,一块儿就能够查询到。
缺点:新增比较慢,由于为了保证表中记录的物理顺序和索引顺序一致,在记录插入的时候,会对数据页从新排序。
索引的逻辑顺序与磁盘上行的物理存储顺序不一样,非汇集索引在叶子节点存储的是主键和索引列,当咱们使用非汇集索引查询数据时,须要拿到叶子上的主键再去表中查到想要查找的数据。这个过程就是咱们所说的回表。
好比汉语字典,想要查「阿」字,只须要翻到字典前几页,a开头的位置,接着「啊」「爱」都会出来。也就是说,字典的正文部分自己就是一个目录,不须要再去查其余目录来找到须要找的内容。咱们把这种正文内容自己就是一种按照必定规则排列的目录称为==汇集索引==。
若是遇到不认识的字,只能根据“偏旁部首”进行查找,而后根据这个字后的页码直接翻到某页来找到要找的字。但结合部首目录和检字表而查到的字的排序并非真正的正文的排序方法。
好比要查“玉”字,咱们能够看到在查部首以后的检字表中“玉”的页码是587页,而后是珏,是251页。很显然,在字典中这两个字并无挨着,如今看到的连续的“玉、珏、莹”三字实际上就是他们在非汇集索引中的排序,是字典正文中的字在非汇集索引中的映射。咱们能够经过这种方式来找到所须要的字,但它须要两个过程,先找到目录中的结果,而后再翻到结果所对应的页码。咱们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为==非汇集索引==。
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
ALTER TABLE `table_name` ADD UNIQUE (`column`)
ALTER TABLE `table_name` ADD INDEX index_name (`column` )
ALTER TABLE `table_name` ADD FULLTEXT (`column`)
ALTER TABLE `table_name` ADD INDEX index_name (`column1`,`column2`,`column3`)
了解了索引的基本概念后,可能最好奇的就是索引的底层是怎么实现的呢?为何索引能够如此高效地进行数据的查找?如何设计数据结构能够知足咱们的要求?
下文经过通常程序员的思惟来想一下若是是咱们来设计索引,要如何设计来达到索引的效果。
可能直接想到的就是用哈希表来实现快速查找,就像咱们平时用的hashmap同样,value = get(key) O(1)时间复杂度一步到位,确实,哈希索引是一种方式。
哈希索引就是采用必定的哈希算法,只需一次哈希算法便可马上定位到相应的位置,速度很是快。本质上就是把键值换算成新的哈希值,根据这个哈希值来定位。
在MySQL经常使用的InnoDB引擎中,仍是使用B+树索引比较多。InnoDB是自适应哈希索引的(hash索引的建立由==InnoDB存储引擎自动优化建立==,咱们干预不了)。
假设要查询某个区间的数据,咱们只须要拿到区间的起始值,而后在树中进行查找。
如数据为:
当查找到起点节点10后,再顺着链表进行遍历,直到链表中的节点数据大于区间的终止值为止。全部遍历到的数据,就是符合区间值的全部数据。
利用二叉查找树,区间查询的功能已经实现了。可是,为了节省内存,咱们只能把树存储在硬盘中。
那么,每一个节点的读取或者访问,都对应一次硬盘IO操做。每次查询数据时磁盘IO操做的次数,也叫作==IO渐进复杂度==,也就是==树的高度==。
因此,咱们要减小磁盘IO操做的次数,也就是要==下降树的高度==。
结构优化过程以下图所示:
这里将二叉树变为了M叉树,下降了树的高度,那么这个M应该选择多少才合适呢?
问题:对于相同个数的数据构建m叉树索引,m叉树中的m越大,那树的高度就越小,那m叉树中的m是否是越大越好呢?到底多大才合适呢?
无论是内存中的数据仍是磁盘中的数据,操做系统都是按页(一页的大小一般是4kb,这个值能够经过getconfig(PAGE_SIZE)
命令查看)来读取的,一次只会读取一页的数据。
若是要读取的数据量超过了一页的大小,就会触发屡次IO操做。因此在选择m大小的时候,要尽可能让每一个节点的大小等于一个页的大小。
通常实际应用中,出度d(树的分叉数)是很是大的数字,一般超过100;==树的高度(h)很是小,一般不超过3==。
顺着解决问题的思路知道了咱们想要的数据结构是什么。目前索引经常使用的数据结构是B+树,先介绍一下什么是B树(也就是B-树)。
以下图所示:
了解了B树,再来看一下B+树,也是MySQL索引大部分状况所使用的数据结构。
这些基本特色是为了知足如下的特性。
由于MongoDB不是传统的关系型数据库,而是以Json格式做为存储的NoSQL非关系型数据库,目的就是高性能、高可用、易扩展。摆脱了关系模型,因此范围查询和遍历查询的需求就没那么强烈了。
MyISAM的索引文件(.MYI)和数据文件(.MYD)文件是分离的,索引文件仅保存记录所在页的指针(物理位置),经过这些指针来读取页,进而读取被索引的行。
树中的叶子节点保存的是对应行的物理位置。经过该值,==存储引擎能顺利地进行回表查询,获得一行完整记录==。
同时,每一个叶子也保存了指向下一个叶子的指针,从而方便叶子节点的范围遍历。
在MyISAM中,主键索引和辅助索引在结构上没有任何区别,==只是主键索引要求key是惟一的,而辅助索引的key能够重复==。
Innodb的主键索引和辅助索引以前提到过,再回顾一次。
InnoDB主键索引中既存储了主健值,又存储了行数据。
对于辅助索引,InnoDB采用的方式是在叶子节点中保存主键值,经过这个主键值来回表查询到一条完整记录,所以按辅助索引检索其实进行了二次查询,效率是没有主键索引高的。
在上一节中了解了索引的多种数据结构,以及B树和B+树的对比等,你们应该对索引的底层实现有了初步的了解。这一节从应用层的角度出发,看一下如何建索引更能知足咱们的需求,以及MySQL索引何时会失效的问题。
先来思考一个小问题。
问题:当查询条件为2个及2个以上时,是建立多个单列索引仍是建立一个联合索引好呢?它们之间的区别是什么?哪一个效率高呢?
先来创建一些单列索引进行测试:
这里创建了一张表,里面创建了三个单列索引userId,mobile,billMonth。
而后进行多列查询。
explain select * from `t_mobilesms_11` where userid = '1' and mobile = '13504679876' and billMonth = '1998-03'
咱们发现查询时只用到了userid这一个单列索引,这是为何呢?由于这取决于MySQL优化器的优化策略。
当多条件联合查询时,优化器会评估哪一个条件的索引效率高,它会选择最佳的索引去使用。也就是说,此处三个索引列均可能被用到,只不过优化器判断只须要使用userid这一个索引就能完成本次查询,故最终explain展现的key为userid。
多个单列索引在多条件查询时优化器会选择最优索引策略,可能只用一个索引,也可能将多个索引都用上。
可是多个单列索引底层会创建多个B+索引树,比较占用空间,也会浪费搜索效率
因此多条件联合查询时最好建联合索引。
那联合索引就能够三个条件都用到了吗?会出现索引失效的问题吗?
该部分参考并引用文章:
建立user表,而后创建 name, age, pos, phone 四个字段的联合索引
全值匹配(索引最佳)。
索引生效,这是最佳的查询。
那么时候会失效呢?
最左匹配原则:最左优先,以最左边的为起点任何连续的索引都能匹配上,如不连续,则匹配不上。
如:创建索引为(a,b)的联合索引,那么只查 where b = 2 则不生效。换句话说:若是创建的索引是(a,b,c),也只有(a),(a,b),(a,b,c)三种查询能够生效。
这里跳过了最左的name字段进行查询,发现索引失效了。
遇到范围查询(>、<、between、like)就会中止匹配。
好比:a= 1 and b = 2 and c>3 and d =4 若是创建(a,b,c,d)顺序的索引,d是用不到索引的,由于c字段是一个范围查询,它以后的字段会中止匹配。
如计算、函数、(手动或自动)类型转换等操做,会致使索引失效而进行全表扫描。
explain select * from user where left(name,3) = 'zhangsan' and age =20
这里对name字段进行了left函数操做,致使索引失效。
explain select * from user where age != 20;
explain select * from user where age <> 20;
索引失效
explain select * from user where name like ‘%zhangsan’;
索引生效
explain select * from user where name like ‘zhangsan%’;
explain select * from user where name = 2000;
explain select * from user where name = ‘2000’ or age = 20 or pos =‘cxy’;
正常(索引参与了排序),没有违反最左匹配原则。
explain select * from user where name = 'zhangsan' and age = 20 order by age,pos;
违反最左前缀法则,致使额外的文件排序(会下降性能)。
explain select name,age from user where name = 'zhangsan' order by pos;
正常(索引参与了排序)。
explain select name,age from user where name = 'zhangsan' group by age;
违反最左前缀法则,致使产生临时表(会下降性能)。
explain select name,age from user where name = 'zhangsan' group by pos,age;
但愿你们可以多去使用索引进行SQL优化,有问题欢迎指出。
来源:宜信技术学院做者:杨亨