likes
comments
collection
share

MySQL索引全解:从理论到实践,打造高效查询的指南

作者站长头像
站长
· 阅读数 38

前言

授人以鱼,不如授之以渔,授人以鱼只救一时之急,授人以渔则可解一生之需。 我一直以为,任何规范只能约束一时,要想真正的解决问题,就一定要搞清楚问题背后的道理,这是我写这篇文章的初衷,我希望开发者在按照规范或者经验去尝试使用索引解决一些问题的同时,也能想清楚这样做是不是对于所有的场景都合适,有没有什么缺点?要知道,如果一味的按照规范去执行,就好像是在按别人的脚码去买鞋,买的是对的,但却不适合自己,规范和经验只是提供了一种指导和参考,而真正的解决方案应该根据实际情况进行量身定制,而本文就是要让大家拥有量身定制的能力。

索引的数据结构

这一节先从索引的数据结构开始讲起,如果说数学是一切科学的基础,那数据结构和算法就是计算机程序的基础,解决同样的问题,用合适的数据结构和算法可能只需要几分钟,而不合适的可能需要好几年,这是量级上的巨大差异。 对于解决数据检索问题的时间复杂度而言,哈希索引通常被认为是最快的方法。然而实际应用场景往往是复杂多变的,它要解决的更多是软件工程问题,而非理论科学问题,所以,仅仅关注算法的时间复杂度是不够的,还要结合实际的应用场景,综合考虑各种因素,以便选择最适合的数据索引方案。

哈希表

我们先来简单了解一下哈希表的数据结构,哈希表是一种以(key,value)形式存储的数据结构,通过哈希函数的方式找到关键字在索引中的位置,使其能支持快速的查找、插入和删除。 哈希表实现简单且检索效率也非常高,像Redis这样的内存数据库就有大量用到了哈希表作为其数据检索的索引。

下图主要展示了Redis中哈希表的数据结构: MySQL索引全解:从理论到实践,打造高效查询的指南 那为什么在MySQL中没有大量应用呢?(MySQL目前只有memory存储引擎支持指定使用哈希索引)

结合MySQL实际要解决的问题,主要有如下几个原因:

  1. 哈希索引无法支持范围查询,通过哈希函数定位到的索引位置是随机的,物理位置上相邻的两个数在逻辑上并没有任何关系,所以像大于、小于这样的范围查询就无法利用哈希索引来提高效率了。
  2. 无法支持排序,道理和前一条中解释的是一样的。
  3. 只能支持完整索引值快速查询,不能加速类似右模糊这样的查询,因为哈希函数计算的是完整的索引值。

上述3个问题都是平常业务中非常常见的场景,因此如果不能满足,则就需要考虑是否还有其他更合适的数据结构了。

这里额外补充一点:InnoDB引擎有一个特殊的能力,叫做自适应哈希索引,也就是当某些索引值使用的非常频繁时,InnoDB会在内存中基于这个值再构建一个哈希索引,这样就能利用哈希索引的优势快速定位,不过这个行为对于客户端来说是不可调整的,客户端能做的就是决定开启还是关闭这个能力(由innodb_adaptive_hash_index参数控制,默认为开启)。

有序数组

有序数组是可以高效的支持范围查询、排序等操作,因为是有序的,所以可以利用二分查找来快速定位元素位置,这样看来,有序数组无论是在单键查询还是范围查询、排序甚至是模糊查询,都是非常高效的,所以,如果只需要满足数据检索的需求,有序数组已经非常合适了。

二分查找: MySQL索引全解:从理论到实践,打造高效查询的指南 不过有序数组必须还得保证当有数据插入、修改或者删除时依然能保证其有序性,这通常需要较高的操作成本,变更影响到的数据越靠前,性能消耗就越大。

插入数据:

所以,对于需要经常插入、删除、修改等操作的数据库来说,有序数组显然也不合适。 MySQL索引全解:从理论到实践,打造高效查询的指南

有没有一种数据结构能够同时解决数据检索和数据更新的问题呢?答案就是:平衡搜索树,实际应用中常见的平衡搜索树有AVL树和红黑树,平衡搜索树会在进行插入和删除操作时,通过调整自身的结构以保持树的高度平衡,从而保证搜索、插入和删除操作的时间复杂度都是O(logN)

AVL树 MySQL索引全解:从理论到实践,打造高效查询的指南 红黑树 MySQL索引全解:从理论到实践,打造高效查询的指南

截止到目前为止,平衡搜索树基本上已经解决了前面两种数据结构遇到的所有问题,实际上Innodb存储引擎选择的也就是平衡搜索树,只不过这个平衡搜索树既不是AVL树也不是红黑树,而是B+ Trees,而选择B+ Trees的理由只有一个:更适合磁盘存储。

详细聊聊B+ Trees

上节中,我们介绍了几种常见的数据检索相关的数据结构,并对其优劣势进行了简单的分析,最终发现平衡搜索树是面对此类问题场景中不错的选择,同时在小节文章最后,也提到了InnoDB存储引擎最终是选择了B+ Trees这样的平衡搜索树来作为其索引的底层数据结构,因此接下来本小节,我们就来了解一下B+ Trees,以及它是如何解决实际应用场景中的问题,最终让InnoDB选择它?

B+ Trees三大杀器

B+ Trees一定是软件工程的产物,它的设计与实现是面向特定应用场景的,它更加注重应用在这些场景中的可行性、稳定性以及运行效果,而最能说明这一点的核心我认为主要体现在下面三个设计中:

1. 树的度(宽度)可以很大

AVL树和红黑树共同存在的一个问题就是他们都是二叉树,无论怎样平衡在解决树的深度问题上的能力都非常的有限,而在B+ Trees(注:后续我们提到的B+ Trees都是指InnoDB中的实现)中,每个节点(InnoDB中支持对此大小进行调整,参数为:innodb_page_size)默认的大小是16KB。假设我们索引的字段是int类型,也就是4个字节,那么也就意味着我们一次性可以读取4096条索引值到内存中(16KB/4byte=4096条,实际情况还要考虑指针等记录所占用的空间)。 对于一个度为:7的树来说,存储22个元素只需要2层,而如果是二叉树则至少需要5层。 MySQL索引全解:从理论到实践,打造高效查询的指南 所以,解决了树的宽度问题,自然也就解决了树的深度问题。

既然这样,是不是可以直接调整innodb_page_size的大小,比如innodb_page_size支持你调整到32KB或者64KB,其实之所以默认是16KB,主要是因为当前主流的磁盘设置,扇区大小就是16KB,保持页的大小与扇区大小一致,可以最大限度的减少将未更改的数据重写到磁盘,此外如果是经常用于频繁写入和读取的场景,较小的page_size也可以减少单个节点被频繁重写到磁盘的情况。

2. 叶子节点存储数据

聚簇索引

叶子节点存储数据这个设计,还有个专门的词语用来描述它:聚簇索引。 聚簇索引并不是一种单独的索引类型,而是一种数据存储的方式,聚簇指的就是数据行和索引值存储在一起。

聚簇索引的优点
  1. 因为数据行和索引键值存放在一起,因此在根据索引找到对应数据页之后,即可直接从该数据页中取出所需要的数据并返回,如果没有使用聚簇索引,则至少还需要再进行一次磁盘I/O
  2. 查询时能使用覆盖索引也是建立在聚簇索引的前提之上才能实现的。
聚簇索引的缺点
  1. 因为需要存储数据行,因此在数据插入和更新时,聚簇索引的设计要明显慢于非聚簇索引,尤其是随机插入或者更新时更为明显。
  2. 由于每个二级索引都会记录主键索引的键值,因此不但会占用更多的存储空间,也会在数据行删除和插入时更加费时。
  3. 由于数据行保存在主键索引的叶子节点上,因此每次根据二级索引查询时,二级索引如果又不是完全覆盖,则还需要再回到主键索引上查询一次,这个过程我们也称为回表查询。
聚簇索引与非聚簇索引对比

MySQL索引全解:从理论到实践,打造高效查询的指南

主键索引和二级索引

对于主键索引与二级索引来说,叶子节点存储的数据还有点不一样,如果是主键索引,叶子节点存储的就是整行数据,而如果是二级索引,叶子节点存储的则是主键ID和索引列数据。

二级索引

类似这样的查询select id from t where phone = ?;因为二级索引的叶子节点除了会记录索引内容本身之外,还会记录主键索引的值,因此如果只是查询id,则直接通过扫描二级索引即可完成。 检索数据至少需要经历2次磁盘I/O MySQL索引全解:从理论到实践,打造高效查询的指南

主键索引

但如果是idname都需要查询:select id, name from t where phone = ?;就会先通过phone这个二级索引定位到主键id,然后再用主键id去主键索引中查找一次,最终才能将idname一起返回。

检索数据至少需要经历4次磁盘I/O(二级索引和主键索引分别2次) MySQL索引全解:从理论到实践,打造高效查询的指南

最后,我们再来想想为什么数据只会保留在叶子节点上?

这其实也是B+ TreesB Trees的一个重要区别,如果将数据也保留在非叶子节点上会有什么问题呢? 问题一:它可能会致使子树节点中存储的数据量急剧减少,导致树的高度增加。 问题二:查询可能出现时快时慢,让系统看起来不是很稳定,运气好可能在树的第二层就拿到了数据(1次磁盘I/O),运气不好就不好说了(因为越往下覆盖的数据就越多,所以可能大多数时候你的运气都会很不好)。 所以,最终的结论就是,数据只保存在叶子节点上可以最大限度的让非叶子节点多存储一些数据,从而避免引发树的深度问题。

3. 叶子节点双向指针记录

这个设计是B+ TreesB Trees另一个重要的区别,双向指针的记录,主要是为了更好的支持范围查询,通过指针的记录,叶子节点之间可以直接顺序遍历到,而不需要再从根节点走下来。

比如:要查询小于等于17的数据。 如果没有指针,那就只能这样查找,需要经过12次查找。 MySQL索引全解:从理论到实践,打造高效查询的指南 有了指针,只需要经过7次查找。 MySQL索引全解:从理论到实践,打造高效查询的指南 至此,我们已经介绍完了B+ Trees的三大杀器,我相信你应该也对B+ Trees有了基本的了解,后面的几小节,我们基本上就会围绕在实际应用中如何遵从并利用这些设计来提升查询效率。 如果你现在觉得这些概念还有些复杂,不能完全理解也没关系,这本身就需要一定的时间,当然,后面的一些实际应用也会反复应用到这些设计,到时候,你再结合本节内容来理解,那么我相信,这些概念就会变得越来越清晰。

覆盖索引

本节,我们先来介绍一个非常好用的优化手段,它能够极大地提高查询的性能:覆盖索引。 覆盖索引并不是一种单独的索引类型,准确的说它只是一种利用数据存储在叶子节点上的优化行为。

举个例子

前面我们提到过,当执行这段select id from t where phone = ?;查询时,因为phone索引本身就记录了主键id的值,所以省去了再去主键索引中查询整行数据的过程,从而提升了查询效率。

也就是说,如果查询所需要的列信息,本身在二级索引的叶子节点中就已经全部包含了,那么像这样的查询我们就称为:覆盖索引查询。

如何利用覆盖索引

要想利用覆盖索引的特性,一般我们会通过两种方式来实现,一种就是在建立索引时考虑,一种就是在执行查询时考虑,下面我们可以分别对这两种情况进行分析。

建立索引时考虑

还是前面的案例select id, name from t where phone = ?;也许在你的业务场景中并不存在直接通过phonename过滤查询数据行信息的情况,所以你觉得没有必要为name字段建立索引,但如果发现有大量的根据phone查询name的需求,那么在这样的情况下,也是可以考虑建立phonename联合索引的,这样一来就可以利用覆盖索引来提高查询效率了。

查询时考虑

常见的案例比如:深度分页查询。 比如按照每页查询10条,查询第1001页:select * from page_test LIMIT 10000,10; 利用覆盖索引过滤部分结果集:select * from page_test a, (select id from page_test LIMIT 10000,10) b where a.id = b.id;

建立索引的原则

建立索引的原则,主要指的是什么情况下应该建索引,在哪些列上建索引,建什么样的索引,建多少索引等方面,这些问题应该是日常开发中最常见的,但凡建一张表就需要考虑上述几个问题。

在哪些列上建索引?

首先,索引列最好要有比较高的区分度,因为只有足够高的区分度才能更好的体现出索引的优势。 可以通过执行show index from table;命令,得到指定表索引的大致信息,其中有一个关键列Cardinality,其表示了该索引列不重复值的个数,因此该值当然越接近表的总记录数越好。 MySQL索引全解:从理论到实践,打造高效查询的指南

不过,这个统计并不是100%准确的,如果你遇到过MySQL选错索引的情况,很有可能就是因为这个统计有误导致的,后面会单独对这个问题进行分析,现在你只要知道有这样一个参考值即可。

其次,一般就是我们常说的涉及到where、group by、order by等查询情况时需要考虑建立索引,但前面在介绍覆盖索引时也提到,要注意利用覆盖索引的能力来加速查询,也是不错的选择。

最后,有一点比较重要,要尽量避免在频繁更新的列上建立索引,因为每一次更新都需要对索引的存储进行维护,尤其是数据页的分裂与合并将会带来比较大的性能消耗。

建什么样的索引?

按索引的建立方式可以分为:主键索引、唯一索引、二级索引、联合索引、全文索引、前缀索引

主键索引

自增主键与业务主键

数据表的主键到底是应该用自增还是直接用业务主键,比如数据表中记录的订单号,本身就必须是唯一的,因此是否可以直接作为数据表的主键? 关于这一点,在大多数情况下,还是建议使用自增主键,这样做的原因主要是因为自增主键通常空间占用更少(每个二级索引都会额外记录主键索引的值),以及自增的有序性保证也可以避免数据页的分裂问题。当然直接使用业务主键作为索引可以在查询该业务字段时减少一次回表的过程,比如:像这样的查询select order_id from t where create_time > ?如果order_id是主键,则直接建立create_time索引即可,否则通常需要建立create_timeorder_id的联合索引。

二级索引与唯一索引的选择

二级索引和唯一索引是两种常见的索引类型,它们各有优缺点。 二级索引不要求索引值在索引记录中只能有一个,这意味着在同一个索引中可以有多个相同的索引值。而唯一索引则要求每个索引值必须唯一,也就是说在同一个索引中每个索引值只能出现一次。这种简单的区别却带来了不同的查询和更新处理方式,是在选择时需要考虑的主要因素。 接下来,我们就来对比一下两种索引在查询和更新上的差异性。

查询

我们先来看看查询,对于唯一索引来说,很明显,一旦查询到数据后,即可直接返回。而如果是二级索引,则需要继续查找当前数据的下一条记录,当下一条记录不能匹配时则可返回。 看起来二级索引因为不能确认当前查询到的结果是否是唯一的,所以需要额外的查询一次下一条记录,但实际上我们应该考虑到由于数据是按页加载到内存中的,因此绝大多数情况下,所谓的获取下一条记录并进行判断只是一次内存中的寻址和简单的计算而已,这样的性能差异几乎是可以忽略不计的。 当然,也有比较特殊的情况,就是匹配到的数据刚好是当前数据页中的最后一条记录,那么再查询下一条记录时则有可能因为下一条记录所在的数据页不在内存中,而需要进行一次从磁盘加载到内存中的操作了,不过,对于一个数据页通常能记录上千条索引值来说,这样的概率也是非常小的。 例如这样的查询select * from t where x = 2二级索引会查到下一页。 MySQL索引全解:从理论到实践,打造高效查询的指南 基于上面的分析,我们可以发现对于查询操作来说,二级索引与唯一索引并没有明显的性能差异。

更新

我们再来对比一下两种索引的更新操作。不过,在这之前需要先简单介绍一下change buffer

什么是change buffer?

为了加快数据更新的速度,在InnoDB中,每次数据更新实际上都会先查询当前要更新的数据页是否已经加载到内存中了,如果是,则直接更新内存中的数据页即可,如果不是,则会先将本次更新记录到change buffer中,这样则能免去一次从磁盘加载数据页的过程。

change buffer的merge

更新记录当然不会一直保留在change buffer中,最终还是需要写到数据页的,change buffer写数据页的过程也被称为change buffermerge过程,通常有如下三种情况会触发change buffermerge操作:

  1. 当前数据页被访问了。
  2. 后台定时merge
  3. 数据库关闭。
change buffer会丢失吗?

当然,也许你也注意到了,如果在change buffer merge之前数据库宕机了,那change buffer中记录的信息岂不是全部都丢失了?这里需要说明一下,change buffer中记录的操作实际上也会记录在redo log里,并随之一起写磁盘,而redo log保证了数据不会丢失。

change buffer也有不适用的场景

change buffer更适合有频繁数据写入的情况,比如批量插入,或者说适合写完以后不会立刻读的情况,反过来,如果写完以后,立刻就要读,那就会触发merge,这样反而多了一次维护change buffer的过程。

再看更新

说了这么多,现在我们可以回到更新这个问题上来了,我们可以分两种情况进行分析:

第一种,如果数据页已经在内存中了,那么对于唯一索引来说,需要先判断前一条和后一条记录是否重复,如果不重复则直接更新即可,对于二级索引来说,无需判断直接更新即可。就和查询时的校验一样,这样的操作性能差异并不大。 第二种,如果数据页不在内存中,那么对于唯一索引来说,必须先将数据页加载到内存中,并进行判断,而对于二级索引来说,则不需要,只要直接写change buffer即可,也就真正利用了change buffer的能力。

结合查询和更新两大场景来看,在大多数情况下二级索引实际上比唯一索引更合适,不过,我们经常也为了解决幂等性问题,通过建立唯一索引来作为最后一道天然的屏障,如果业务上有这样的需要,那就建立唯一索引,不必在意change buffer的问题。

联合索引

如果明确有多个列会频繁组合在一起查询时,那么为这些列建立联合索引则会比较高效,就好比原本你需要到多个索引文件中挨个检索的情况,现在只需要在一个索引文件中就能完成。

除了你本身已经识别出来的情况,通常如果你在执行计划Extra列中看到比如:Using union 、Using intersect等关键字时就是在提醒你应该考虑建立联合索引了。

联合索引要注意什么?

  1. 遵守最左匹配原则。
  2. 区分度高的尽量放在最左边(注意:这不是绝对,以后在聊到SQL优化时会提到这一点)。
  3. 联合索引列数不宜过多,很明显,列数过多就像字段长度太长一样,从对磁盘和内存的使用来说,都不是好的选择。

全文索引

全文索引是一种特殊的索引,它主要是用来在一段文本中查找关键字,而不是直接比较索引中的值,它和前面几类索引完全不一样,全文索引更类似于搜索引擎做的事,使用时你得关心停用词、词干等,而不是简单的where查询。 不过全文索引和前面这些索引并不冲突,也就是说你可以在同一个列上同时创建二级索引和全文索引。

前缀索引

因为索引本身也是需要存储的,所以索引长度首先影响的就是存储空间(不局限于磁盘上的存储,也关系到内存中的数据页能一次性加载多少索引键值)。

一般如果要在一些前缀重复度较高的列上加索引时(比如城市地址、有固定前缀规则的编号等),可以考虑使用前缀索引,这样有时候不但可以提升索引性能,还能节省索引空间。

前缀索引最关键的就是找到一个合适的索引长度,兼顾索引的选择性以保证查询效率,索引的选择性指的是不重复的索引值和数据表的总记录的比值,选择性越高查询效率也就越高,比如唯一索引的选择性就是1。

关于选择性的计算

下面这个语句是一个索引选择性计算常用的查询方式: select count(distinct left(索引列,索引长度))/count(*) from table;

SELECT count(DISTINCT LEFT(order_no, 20)) / count(*) AS '20', count(DISTINCT LEFT(order_no, 22)) / count(*) AS '22', count(DISTINCT LEFT(order_no, 24)) / count(*) AS '24', count(DISTINCT LEFT(order_no, 26)) / count(*) AS '26', count(DISTINCT LEFT(order_no, 28)) / count(*) AS '28', count(DISTINCT LEFT(order_no, 30)) / count(*) AS '30', count(DISTINCT LEFT(order_no, 32)) / count(*) AS '32' FROM test; MySQL索引全解:从理论到实践,打造高效查询的指南 order_no字段在设置时长度为32,可以看出长度从26开始,区分度已经接近1,所以此时再增加长度的性价比已经不高了。

使用前缀索引值得注意的地方

  1. 由于数据是不断变化的,因此之前计算得到的选择性可能因为数据的变化而变化,最终导致性能越来越差。
  2. 选择性的计算也许并不够准确,比如从上面的结果的来看,长度26已经比较合适了,但实际上这只是一个平均值,你要确保数据分布的是否足够均匀,否则你可能会掉入陷阱。
  3. 一旦使用了前缀索引你就失去了order by、group by以及覆盖索引等提升查询性能的能力了。

索引有序性

我们再来额外谈谈关于索引的有序性问题,为什么我们都说要尽量避免使用像UUID这样的数值作为聚簇索引,除了他本身比较占用空间之外,还有一个更重要的原因就是UUID是无序的,无序性会带来更严重的问题,它使得聚簇索引的插入变得完全随机,没有任何聚集特性可言,也将导致很多围绕程序局部性原理的设计变得无效。

建议自己先试验一下使用自增主键,与使用UUID作为主键两种方式在数据插入上的性能差异,尤其是当索引大小超过服务器内存时。

如果索引值是顺序的,那么当一个数据页满了以后(实际上不会完全满,InnoDB默认的最大填充因子是页大小的15/16),只需要将数据插入下一个新页即可。 如果索引值是随机的,那么就无法简单的把数据插入最后,InnoDB得先为数据找到合适的位置,这会带来以下几个问题:

  1. 可能目标数据页已经刷到磁盘上了,那么InnoDB就必须先将数据页从磁盘加载到内存中,这将会产生大量随机I/O
  2. 因为数据的顺序是随机的,所以可能会产生大量的页分裂操作,页分裂会导致需要移动大量的数据。
  3. 页分裂过多的结果又会造成数据页变得稀疏,最终可能又会触发数据页合并,数据页合并又会导致需要移动大量的数据。

一棵被无序插入的索引树 MySQL索引全解:从理论到实践,打造高效查询的指南 插入:6,第一个数据页变稀疏了,只记录1、2、3,并且涉及到了3个数据页的改动。 MySQL索引全解:从理论到实践,打造高效查询的指南

使用optimize table命令可以重新优化数据页的使用情况。

建立多少索引?

显然,索引的数量并不是越多越好,这是因为索引的增加会对数据库的写入性能产生不利影响,每一个索引都会有一份独立的存储数据,索引越多也就意味着同时要维护的索引文件越多,这会对系统造成不小的消耗,因此,索引的数量需要控制在合理的范围,以便最大化提高查询性能,并同时减少索引维护的成本。 此外,索引越多也意味着要存储的内容越多,就需要更多的磁盘空间来存储。

如果是非聚簇索引还会有这个问题吗?

所以,我认为建多少索引主要还是需要结合实际的业务场景来定夺,你只要清楚的了解其中的利弊就可以了。

不过有一类场景可以提醒一下,就是类似归档表这样的数据表,有时这样的表可能并不需要像原表一样为了满足业务查询建立许多索引。减少一些不必要的索引,不但能够加快归档的速度,同时还能减少存储的空间。

为什么会选错索引?

不知道你有没有遇到过由于MySQL选错索引而导致查询变慢的情况,并且有时候选错的情况看起来非常的傻,让人不能理解,如此智能的优化器为什么会犯这么简单的错误?好了,本节我们就是来搞清楚这个问题的。 首先,不得不说,绝大多数情况下你不要以为自己比优化器要厉害,优化器已经完成了上百个优化项,下面列举了几个优化类型:

  1. 把取最大、最小当作一个常数条件,直接获取索引的第一条或者最后一条记录。 MySQL索引全解:从理论到实践,打造高效查询的指南
  2. 因为id是表的主键,因此优化器能确定最多只会返回一条数据,所以where条件可以被当作常量类型处理。 MySQL索引全解:从理论到实践,打造高效查询的指南
  3. 还有很多语句上的改写,比如: 1=1 and x > 1会被直接改为 x > 1(a < b and b = c)and a = 5会被改为b > 5 and b = c and a = 5 MySQL索引全解:从理论到实践,打造高效查询的指南

优化器成本模型

MySQL的优化器使用的是一种基于成本模型的方法,用于评估和优化查询性能。其中一个重要的成本指标就是本次查询需要扫描的行数。这个指标对于优化器作出正确的决策非常关键,它直接影响了对于查询成本的评估。如果统计不准确,优化器就可能会产生错误的结果。

前面我们也提到过可以通过show index from table;得到一个索引的大致信息,其中有一个关键列Cardinality,其表示了该索引列不重复值的个数(非精确统计),因此该值当然越接近表的总记录数越好。

现在我们有必要了解一下这个Cardinality是怎么得来的,实际上,InnoDB引擎是通过抽样的方式来计算这个基数信息的,首先读取少量的数据页(具体数据量由innodb_stats_sample_pages来设置),统计这些数据页上的数值,得到一个平均值,然后乘以索引总共的数据页,就得到了这样一个基数。

当然,数据表是会变化的,所以索引的统计信息也必须跟着变,触发重新统计的条件大概有下面几种:

  1. 超过一定数量的数据行产生了变化。
  2. 执行analyze table、show table status、show index等这样的命令。

除了这个索引统计信息不准确会导致选择错误之外,还有如下一些情况可能会导致MySQL优化器选错执行计划:

  1. 成本指标并不完全等同于运行时的实际成本,例如执行计划可能预估需要读取更多的页面,但实际读取时可能成本却很低,因为如果读取的页都是顺序读取或都已经在内存中的话,那么它的实际访问成本将降低,而优化器并不知道哪些页在内存中,哪些在磁盘上。
  2. 基于成本模型并不意味着执行的时间就是最快的。
  3. 用户自定义的函数或者执行存储过程这些优化器并不会考虑其成本,因为这些不受其控制。

索引的优点

最后我们总结一下索引的优点,索引不仅可以提高数据定位的速度,而且还可以提高数据排序,数据分组的速度,总的来说索引主要是通过以下两种方式来提升性能的:

  1. 减少扫描数据行的数量。
  2. 将随机I/O变为顺序I/O。 对于提高数据检索效率的通用思路,一般都是以上述两个方面为基础的。一方面是通过优化索引设计来实现,选择合适的数据结构和算法,另一方面则是在了解其实现原理之后选择合适的查询语句以及优化技巧,以确保索引的效果最大化。

总之,通过优化索引设计和合理运用查询语句,可以大大提高数据检索效率,实现更加高效、准确、快速的数据检索服务。

转载自:https://juejin.cn/post/7292806211211296822
评论
请登录