前言

整理过了MySQL架构、日志系统、事务和锁机制,本篇的主角就是面试必问的索引了。索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。在数据十分庞大的时候,索引可以大大加快查询的速度。这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据或对应的物理地址然后访问相应的数据。

语法回顾

创建索引

  • 创建表的同时创建索引
1
2
3
4
5
CREATE TABLE demo(  
ID INT NOT NULL,
username VARCHAR(16) NOT NULL,
INDEX [indexName] (username(length))
);
  • 向表中添加索引
1
ALTER TABLE demo ADD [UNIQUE] INDEX index_name(column_name);

或者

1
CREATE INDEX index_name ON demo(column_name);

注意:索引需要占用磁盘空间,因此在创建索引时要考虑到磁盘空间是否足够;创建索引时需要对表加锁,因此实际操作中需要在业务空闲期间进行。

删除索引

1
DROP INDEX my_index ON demo;

或者

1
ALTER TABLE demo DROP INDEX index_name;

查看索引

  • 查看表中的索引:
1
SHOW INDEX FROM tablename
  • 查看查询语句中使用索引的情况
1
EXPLAIN DQL

优缺点

  • 优点
    可以快速检索,减少I/O次数,加快检索速度;根据索引分组和排序,可以加快分组和排序。
  • 缺点
    索引本身也是表,因此会占用存储空间,一般来说,索引表占用的空间是数据表的1.5倍;索引表的维护和创建需要时间成本,这个成本随着数据量增大而增大;构建索引会降低数据表的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表。

常见类型

  • 主键索引
    根据主键建立的索引,主键值不允许重复,非空;关键词*PRIMARY KEY *
1
ALTER TABLE 'table_name' ADD PRIMARY KEY index_name('col');
  • 唯一索引
    索引列的值必须唯一,可以为空;关键词UNIQUE
1
ALTER TABLE 'table_name' ADD UNIQUE index_name('col');
  • 普通索引
    没有任何限制;关键字INDEX
1
ALTER TABLE 'table_name' ADD INDEX index_name('col');
  • 组合索引
    用多个列组合构建的索引
1
ALTER TABLE 'table_name' ADD INDEX index_name('col1', 'col2' 'col3');

遵循最左前缀原则,把最常用作为检索或排序的列放在最左边,依次递减,组合索引相当于建立了col1, col1col2, col1col2col3三个索引。

在使用组合索引的时候可能因为列名长度过长为导致索引的key太大,导致效率降低,在允许的情况下,可以只取前几个字符作为索引,语法如下:

1
ALTER TABLE 'table_name' ADD INDEX index_name(col1(4), col2(3));
  • 全文索引
    用大文本对象的列构建的索引;关键词FULLTEXT INDEX。关于全文索引,由于用得不多这里不多阐述,之前有在个人笔记上整理过: 全文索引介绍传送门

实现原理

索引是在存储引擎层实现的,MySQL支持诸多引擎,而各种存储引擎对索引的支持和实现也各不相同,因此也就有了索引模型的概念。

常见模型

索引的出现是为了提高查询效率,归根结底,还是其数据结构带来的读写效率的增益,这里整理三种较为常见的数据结构:哈希表、有序数组和搜索树

哈希表

哈希表是一种健-值(key-value)存储数据结构,只需要输入待查找的key,就可以找到对应的value。哈希的思路很简单,把值放在数组里,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置。不可避免地,多个key经过hash换算出现同一个值的情况,处理这种情况一般是拉出一个链表来。

对于哈希表应该非常熟悉了,也无须多做整理,直接上结论。MySQL中Memory引擎默认使用哈希索引,InnoDB不支持手动创建;但是InnoDB会自调优:如果觉得建立自适应哈希索引(Adaptive Hash Index, AHI)能提高查询效率,InoDB就会在自己的内存缓存区开启一块区域建立AHI以加速查询。

我们知道哈希表对于一个给定key查询value的效率是非常之高的,但是缺点是,因为它不是有序的,所以做区间查询的速度是很慢的!因此,哈希表这种结构适用于只有等值查询的场景

有序数组

说到等值查询和范围查询,在这两个方面,有序数组的性能都非常优秀。由于数组中的数值是按顺序递增或者递减地存储的,在等值查询场景可以使用二分法快速得到;显然对于范围查询,同样只需线用二分法找出一个边界然后朝一个方向遍历直至另一个边界即可。

如果仅仅看查询的效率,那么有序数组可以说就是最好的数据结构了。但是,在需要更新数据的时候就麻烦了,往数组中间插入或者删除一个数据,就必须得挪动后面的所有数据位置,维护成本过高。

因此,有序数组索引只适用于静态数据存储引擎,比如你要保存的是某一时刻快照数据记录,或者类似2020年杭州市所有人口信息这种不会再更改的数据。

搜索树

最经典结构的莫过于二叉搜索树了,二叉搜索树的特点是:每个节点的左儿子小于父节点,父节点小于右儿子。为了维护查找的复杂度O(log(N)),还需要保持这颗树是平衡二叉树。为了做这个保证,更新的时间复杂度也是O(log(N))。图示如下:

树有二叉也可以有多叉。多叉即每个节点有多个儿子,子节点之间的大小保证从左至右递增。二叉树是搜索效率最高的,但是实际上大多数据库存储并不是用二叉树。原因是索引不止存在内存中,还要写到磁盘上。可以想象一下一个100万节点的平衡二叉树,树高度为20。一次查询可能需要访问20个数据块,且为随机读。在HDD时代,磁盘随机读取一个数据块约10ms寻址时间,也就是说在这个二叉树下单独访问一个行可能需要20*10ms时间,这个查询是很慢的。

因此,为了让一个查询尽量地少读磁盘,就必须让查询过程尽量少的访问数据块。那么就应该使用N叉树,这里N就取决于数据块的大小。以InnoDB中一个整型字段索引为例,N差不多为1024,当这棵树的高度为4时就可以存储1024^3 个值,这已经10+亿了。考虑到树根的数据块总是在内存中的,一个10亿行的表上一个整数字段的索引,查找一个值最多只需要访问3次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了。

N=1024是怎么估算出来的?
存储引擎的设计专家巧妙的利用了磁盘的存储结构。磁盘的最小存储单位是扇区(sector);系统从磁盘中读取数据到内存是以磁盘块(block)为基本概念的,常是整数倍的sector;操作系统以(page)为单位管理内存,一页(page)通常默认为4K。数据库中也有页的概念用于管理内存,其大小通常取操作系统页的整数倍。InnoDB存储引擎默认的每个page的大小为16KB,可以通过参数innodb_page_size将也的大小设置为4K、8K、16K,在MySQL中可以通过如下命令查看页的大小:

1
show variables like 'innodb_page_size';

索引结构的节点被设计为一个页的大小–InnoDB为例即默认16KB,整型字段一般为4B(INT)或8B(BIGINT),指针类型一般为4B或8B,都按8B算,即一个page中大概可以存储16KB/(8B+8B)=1K个值,InnoDB使用B+树实现索引,所有数据皆在子节点上,因此一棵高度4的B+树子节点可维护1024^3 条记录。

索引的实现

前面已经提到InnoDB使用B+树(B+Tree)结构实现的索引,讲到B+Tree索引,我又不得不提一嘴B树索引(B-Tree),面试中确实容易问到哈!所以关于B-Tree索引我也简单整理了一下。

B-Tree索引

B-Tree是多路平衡搜索树,先看B-Tree的数据结构:假设树的阶为M,高度为h,那么B-Tree必须满足以下条件:

  • 每个节点最多有M个孩子,内部结点至少有ceil(M/2)个孩子
  • 叶子节点高度相同,为h
  • 非叶子节点由n个key和n+1个指针组成,相关间隔,关键字key的个数n满足ceil(M/2)-1 <= n <= M-1
  • 且关键字升序排序
  • 叶子节点指针都为null
    非叶子节点的key都是[key, data]二元组,其中key表示作为索引的键值,data为键值所在行的数据

    在B-Tree的结构下,就可以使用二分查找法查找,查找复杂度为h * log(n);一般来说树的高度不高,因此B-Tree是一个非常高效的查找结构。

B+Tree索引

B+Tree是B-Tree的一个变种,同样M阶B+Tree的不同在于:

  • B+Tree的非叶子节点不存储数据,只存储键值
  • B+Tree的叶子节点没有指针,所有的键值都会出现在叶子节点上,且key存储的键值对应data数据的(物理地址|主键|行数据)
  • B+Tree的每个非叶子节点由n个键值key和n个指针组成,n取值范围ceil(M/2) <= n <= M

B+Tree对比B-Tree的优点

  • 磁盘读写代价更低

一般来说B+Tree比BTree更适合实现外存的索引结构,索引结构的节点被设计为一个数据库页的大小,利用外存的“预读取”原则,每次读取的时候,把整个节点的数据读取到内存中,然后在内存中查找,尽可能少的磁盘I/O,B+Tree的非叶节点中不存储data,就可以存储更多的key,每个节点中的key个数越多,那么树的高度越小,需要I/O的次数越少,因此一般来说B+Tree比B-Tree更快(不是绝对的)。

  • 查询速度更稳定

由于B+Tree非叶子节点不存储数据(data),因此所有的数据都要查询至叶子节点,而叶子节点的高度都是相同的,因此所有数据的查询速度都是接近的。

B+Tree索引的优化

很多存储引擎在B+Tree的基础上进行了优化,添加了指向相邻叶节点的指针,形成了带有顺序访问指针的B+Tree,这样做是为了提高区间查找的效率,只要找到第一个值那么就可以顺序的查找后面的值。

存储引擎中的实现

整理好了MySQL索引结构的实现原理,我们整理一下常见的两种存储引擎MyISAM和InnoDB中具体的实现。MyISAM和InnoDB中分别实现了非聚簇索引聚簇索引

这两种索引下面详细解释,解释之前先预备几个概念:在索引的分类中整理过主键索引等其他一众索引,我们把主键索引称为“主索引”,其他的索引统称为“辅助索引”。可以看到主索引只能有一个,辅助索引可以有多个。

非聚簇索引

非聚簇索引的解释是:非聚簇索引顺序与数据物理排列顺序无关

MyISAM中使用的非聚簇索引,其主索引和辅助索引几乎是一样的,只是主索引不允许重复且不允许空值。他们的叶子节点的key都存储着指向键值对应的数据物理地址。如下图所示:

从图中可以看出,非聚簇索引的数据表和索引表是分开存储的。非聚簇索引的数据是根据数据的插入顺序保存。因此非聚簇索引更适合单个数据的查询。插入顺序不受键值影响。

聚簇索引

聚簇索引的解释是:聚簇索引的顺序就是数据的物理存储顺序

InnoDB中使用的聚簇索引,聚簇索引的主索引的叶子结点存储的是键值对应的数据本身辅助索引的叶子结点存储的是键值对应的数据的主键键值。在InnoDB里,非主键索引也被称为二级索引(secondary index)。因此主键的值长度越小越好,类型越简单越好。

主键索引结构 – 数据和主键索引存在一起

辅助索引结构 – 叶子节点存储主键索引

从图中可以看出,二级索引查找数据时需要现在索引树中扫描找出主键,(如果需要)再从主键索引树中扫描一次找到相应的数据,这个过程就是回表。也就是说基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。

索引维护

索引的维护以聚簇索引来讲。聚簇索引的数据是根据主键的顺序保存。因此适合按主键索引的区间查找,可以有更少的磁盘I/O,加快查询速度。B+树为了维护索引有序性,在插入新值的时候需要做必要的维护。但是也是因为这个原因,聚簇索引的插入顺序最好按照主键单调的顺序插入,否则会频繁的引起页分裂,严重影响性能。

页分裂:往B+树中插入数据时,会根据数据的值计算出插入位置,但并不是插入以后就结束了,如果所在的节点满了(对应的数据页满了),根据B+树算法会申请一个新的数据页,然后挪动部分数据过去。这个过程称为页分裂。这种情况下,性能自然会受影响。除此之外,页分裂还影响了数据页的利用率,即原本放在一个页中的数据现在分到了两个页中。
当然有分裂就有合并。当相邻两个节点由于删除了数据,两个页的利用率很低之后,根据B+树算法,会将节点(数据页)做合并。合并的过程,可以认为是分裂过程的逆过程。
有兴趣可以了解一下另一篇有关B+Tree算法的个人整理的笔记:B-Tree和B+Tree详解传送门

在InnoDB中,如果只需要查找索引的列,就尽量不要加入其它的列,这样会提高查询效率。(覆盖索引

一些建表规范里面要求建表语句里一定要有自增主键。当然事无绝对,我们来分析一下哪些场景下应该使用自增主键,而哪些场景下不应该。

自增主键是指自增列上定义的主键,在建表语句中一般是这么定义的: NOT NULL PRIMARY KEY AUTO_INCREMENT。插入新记录的时候可以不指定ID的值,系统会获取当前ID最大值加1作为下一条记录的ID值。

也就是说,自增主键的插入数据模式,正符合了我们前面提到的递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。

而有业务逻辑的字段做主键,则往往不容易保证有序插入,这样写数据成本相对较高。

除了考虑性能外,我们还可以从存储空间的角度来看。假设你的表中确实有一个唯一字段,比如字符串类型的身份证号,那应该用身份证号做主键,还是用自增字段做主键呢?

由于每个非主键索引的叶子节点上都是主键的值。如果用身份证号做主键,那么每个二级索引的叶子节点占用约20个字节,而如果用整型做主键,则只要4个字节,如果是长整型(bigint)则是8个字节。显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。所以,从性能和存储空间方面考量,自增主键往往是更合理的选择。

有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的:

  1. 只有一个索引;
  2. 该索引必须是唯一索引。

没错,就是典型的KV场景。由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。

相关概念

回表

回顾一下回表的概念,一句话概括就是使用二级索引(辅助索引、普通索引)查找到主键后回到主索引树搜索的过程就是回表。如果查询结果所需要的数据只在主键索引上有,就不得不回表。那么,有没有看你经过索引优化,避免回表过程呢?有,其实上面也提到了,就是覆盖索引

覆盖索引

如果在索引树(二级)直接可以提供我们要查询的数据,那就不需要回表。也就是说这个索引“覆盖”了查询需求,这就是覆盖索引。由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段

最左前缀原则

组合索引中的一个原则,在一些联合条件搜索时,为了提高效率同时避免为每个字段都建立索引,可以考虑建立组合索引。建立组合索引后,如果其他的查询只用到了该组合中的一个或部分字段查询,就可以直接利用它的最左前缀来提高查询效率,而不用单独建立索引。

用一个(name, age)这个联合索引来分析讨论,下图是(name, age)索引示意图:

可以看到,索引项是按照索引定义里面出现的字段顺序排序的。当需求是查询到所有名字是“张三”(name=‘张三’)的人时,可以快速定位到ID4,然后遍历得到所有结果。或者你要查询所有姓“张”(name like ‘张%’)的人,同样也能够用上这个联合索引。

也就是说,不是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以时联合索引的最左N个字段,也可以时字符串索引的最左N个字符。

到这,就需要注意一个问题:在建立联合索引的时候,如何安排索引内的字段顺序。一般是将索引的复用能力作为评估的标准。例如(a,b)索引建立后,一般情况下不需要单独建立a索引了。因此第一原则是:如果通过调整顺序可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。其次的原则是空间问题,即优先使用较少的空间代价的索引。

索引下推

说到满足最左前缀时可以用于在索引中定位记录,那不符合最左前缀的部分,会怎么样呢?为了说明这个问题,还是用上图(name,age)联合索引为例,需求:检索出姓张的且年龄是10岁的人。SQL如下:

1
select * from usert where name like '张%' and age=10;

已知满足最左前缀,所以先用‘张’找到第一个满足条件的记录ID3,然后呢?然后当然是去判断age条件是否满足。但是,这里重点注意一下但是:

在MySQL5.6之前,只能从ID3开始一个个回表,到主键索引树上找出数据行,再去比对age。而5.6版本引入的索引下推(index condition pushdown)优化,可以在索引遍历过程中,对索引中包含的字段优先做判断,直接过滤掉不满足条件的记录,减少回表次数。示意图如下:

上图示意无索引下推流程(刻意隐去age字段表示InnoDB不会去比对age值)

上图示意索引下推流程(减少了2次回表过程)

change buffer

整理过了主键索引和辅助索引的区别,也介绍了联合索引以及联合索引顺序和索引下推,接下来,想象一下这个场景:一个用户系统,需要按照身份证号查找用户,业务中可以身份证号的唯一性,我们一定会在身份证号字段上加索引,身份证号字段比较大,首先不考虑做主键,那么是选择唯一索引还是普通索引呢?

从逻辑上讲,业务已经保证了它的唯一性,无论选择唯一索引还是普通索引都是正确的。那么他们的差别只能是性能上的,毋庸置疑,普通索引性能更高,接下来就从查询和更新两个方面来一探究竟。假设有一张表t主键id,待定类型索引字段k:

查询过程

假设执行查询的语句是select id from t where k=1;。这个语句在索引树上的查询过程,先从B+树根节点开始,搜索到叶子节点,然后可认为数据页内部用二分法查找记录。

  • 若k是普通索引,查找到满足条件的第一个记录后,需要查找下一个记录,直到碰到第一个不满足k=1条件的记录。
  • 若k是唯一索引,由于索引定义了唯一性,查找到第一个满足条件的记录后就会停止检索。

那么这个不同带来的性能差距是多少呢?– 微乎其微。前面提到过,InnoDB的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘中读出来,而是以页为单位,将其整体读入内存。所以说,当找到k=1的记录的时候,它所在的数据页都在内存里了,那么对于普通索引来说,只需要多一次内存查询判断。当然,如果k=1正好是这个数据页最后一个记录,那就要取下一个数据页,这样会稍微复杂一些。但是别忘了,这个概率也是微乎其微的,之前计算过一个页可以放近1K个key。因此,在计算平均性能差异时,仍可以认为这个操作成本对于CPU来说可以忽略不计。

更新过程

查询影响微乎其微,那么就看看更新的性能差异。在说明这个问题前,必须先了解change buffer的概念。

当需要更新一个数据页时,如果数据页恰好在内存中,那就直接更新,但是如果这个页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB会将这些更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中于这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

需要说明的是,虽然名字是change buffer,实际上他是可以持久化的数据,也会被写入磁盘上。将change buffer中的操作应用到原始数据页,得到最新结果的过程称为merge。除了访问这个数据页会触发merge外,系统会定期merge。数据库正常关闭的过程中也会触发merge。

显然,如果能够将更新操作先记录在change buffer,减少磁盘IO读,语句的执行速度会得到明显的提升。而且,数据读入内存中是需要占用buffer pool的,所以这种方式还能避免占用内存,提高内存利用率。那么,什么条件下才能使用change buffer呢

对于唯一索引来说,所有的更新操作都要先判断这个操作是违反唯一性约束。比如需要插入一条k=2的数据,那就需要判断现在表中是否已经存在k=2的记录,而这必须要将数据页读入内存才能判断。如果已经读入了内存,那直接更新就可以了,没有必要使用change buffer了。

因此,唯一索引的更新不能使用change buffer,因为他一定要读入内存去做唯一性校验。实际上,也只有普通索引能使用。

change buffer用的是buffer pool里的内存,因此不能无限增大。可以通过参数innodb_change_buffer_max_size来动态设置,单位为百分比。

change buffer的概念介绍完了,理解了change buffer的机制,回过头看下表t插入一条k=2的记录处理流程:

  • 情况1:目标数据页在存在中

    • 若k为唯一索引,找到k=2的目标位置,判断没有冲突,插入这个值,语句执行结束。

    • 若k为普通索引,找到k=2的目标位置,插入这个值,语句执行结束。

      这样看来,两种索引对更新语句的性能影响,也仅仅是一个判断,只会耗费微笑CPU时间。但这不是真正关注的点。

  • 情况2:目标数据页不在存在中

    • 若k为唯一索引,找到目标节点,将目标节点所在数据页读入内存,找到k=2的目标位置,判断没有冲突,插入这个值,语句执行结束。
    • 若k为普通索引,则是将更新记录在change buffer中,语句执行就结束了。

将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。

change buffer使用场景

已经知道了change buffer只限于用在普通索引的场景下,而不适用于唯一索引。那么,现在有一个问题就是:普通索引的所有场景,使用change buffer都可以起到加速作用吗?

因为merge的时候是真正进行数据更新的时刻,而change buffer的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做merge之前,change buffer记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。

因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时change buffer的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。

反过来,假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在change buffer,但之后由于马上要访问这个数据页,会立即触发merge过程。这样随机访问IO的次数不会减少,反而增加了change buffer的维护代价。所以,对于这种业务模式来说,change buffer反而起到了副作用。

因此,如果对于所有的更新后面,都马上伴随着对这个记录的查询,那么你应该关闭change buffer。而在其他情况下,change buffer都能提升更新性能。普通索引和change buffer的配合使用,对于数据量大的表的更新优化还是很明显的。

change buffer && redo log

理解了change buffer的原理,你可能会联想到redo log和WAL。我发现这两个容易混淆,他们的核心机制都是尽量减少随机磁盘IO,所以觉得有必要整理区分一下。redo log 主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗

索引使用策略

什么时候使用索引?

  • 主键自动建立唯一索引
  • 经常作为查询条件(在where或者group by语句后面)的列要建立索引
  • 作为排序的列要建立索引
  • 查询中与其他表关联的字段,外键关系建立索引
  • 高并发情况下倾向于组合索引
  • 用于聚合函数的列可以建立索引,例如使用了max(column_1)或者count(column_1)时的column_1就需要建立索引
  • 尽量不要包括多列排序,如果一定要,最好为这队列构建组合索引

什么时候不需要建立索引?

  • 经常增删改的列不要建立索引
  • 很多重复值的列
  • 小表不建议使用,没有意义
  • 列值为大文本内容,使用短索引 或 全文索引

索引失效

  • 在一个select语句中,索引只使用一次,如果在where中使用了,那么在order by中就不要用了
  • LIKE语句中,’%word%’会使索引失效(解决方法–覆盖索引(亲测) 或 使用全文索引),但是’word%’有效
  • 在查询条件中使用IS NULL或者IS NOT NULL可能会导致索引失效。(亲测IS NOT NULL只有possible_keys, key为空,IS NULL暂未测出)
  • 在索引的列上使用表达式或者函数会使索引失效(亲测)
  • 字符串不加单引号会导致索引失效。(亲测,只有possible_keys, key为空)更准确的说是类型不一致会导致失效
  • 如果排序的字段使用了索引,那么select的字段也要是索引字段,否则索引失效。但是,如果排序的是主键索引则select * 不会导致索引失效
  • 在查询条件中使用OR连接多个条件会导致索引失效,除非OR链接的每个条件都加上索引,这时应该改为两次查询,然后用UNION ALL连接起来。(亲测,只有possible_keys, key为空)
  • 在查询条件中使用不等于,包括 < 符号、> 符号和 != 会导致索引失效。特别的是如果对主键索引使用!=则不会使索引失效,如果对主键索引或者整数类型的索引使用<符号或者>符号不会使索引失效

小结

  • 使用主索引的时候,更适合使用聚簇索引,因为聚簇索引只需要查找一次,而非聚簇索引在查到数据的地址后,还要进行一次I/O查找数据。
  • 聚簇索引中,基于非主键索引的查询需要多扫描一棵索引树(回表)。因此,我们在应用中应该尽量使用主键查询。
  • 聚簇辅助索引存储的是主键的键值,因此可以在数据行移动或者页分裂的时候降低成本,因为这时不用维护辅助索引。但是由于主索引存储的是数据本身,因此聚簇索引会占用更多的空间。
  • 聚簇索引在插入新数据的时候比非聚簇索引慢很多,因为插入新数据时需要检测主键是否重复,这需要遍历主索引的所有叶节点,而非聚簇索引的叶节点保存的是数据地址,占用空间少,因此分布集中,查询的时候I/O更少,但聚簇索引的主索引中存储的是数据本身,数据占用空间大,分布范围更大,可能占用好多的扇区,因此需要更多次I/O才能遍历完毕。