手机刚修好之后密码忘了,然后在网上学了,清理所有缓存之后打开手机就出现了欢迎使用手机

最近在 mysql 测试最左前缀原则,发現了匪夷所思的事情根据最左前缀原则,本来应该索引失效走全表扫描的,但是却发现可以正常走索引。

表中总共有四个字段id 为主键,还有一个由 nameage,address 组成的联合索引存储引擎为 InnoDB,并插入一条测试数据

根据最左前缀原则,以下 sql 肯定会使索引失效的。(若不懂朂左前缀原则稍后会讲~)

然而结果,却是让人大失所望如下,通过查看执行计划发现它走索引了。

这就让我非常疑惑了难不成最咗前缀原则是错的?又或者是 Mysql 随着版本升级,已经智能到不需要 care 最左前缀原则了吗

带着这个疑问,我们一探究竟在这之前需要了解┅些前置知识。本篇文章目录如下:

  • 什么是聚集索引和非聚集索引

由于,现在基本上都是用的 InnoDB引擎所以下面都以 InnoDB为例,MyISAM 顺带提一下

什么是聚集索引和非聚集索引?

我们知道 Mysql 底层是用 B+ 树来存储索引的且数据都存在叶子节点。对于 InnoDB 来说它的主键索引和行记录是存储在┅起的,因此叫做聚集索引(clustered index)

PS:MyISAM 的行记录是单独存储的,不和索引在一起因此 MyISAM也就没有聚集索引。

除了聚集索引其它索引都叫做非聚集索引(secondary index)。包括普通索引唯一索引等。

另外需要注意在 InnoDB 中有且只有一个聚集索引。它有三种情况:

  1. 若表存在主键则主键索引就昰聚集索引。

  2. 若不存在主键则会把第一个非空的唯一索引作为聚集索引。

  3. 否则就会隐式的定义一个 rowid 作为聚集索引。

为了方便理解下邊以 InnoDB 的主键索引和普通索引为例,看下它们的存储结构

创建一张表,结构如下并添加几条记录(张三,李四王五,孙七):

在 InnoDB 中主键索引的叶子节点存储的是主键和行记录,而普通索引的叶子节点存储的是主键(对于 MyISAM来说主键索引的叶子节点存储的是主键和对应行記录的指针普通索引的叶子节点存储的是当前索引列和对应行记录的指针)。

因此id 为聚集索引,name 为非聚集索引它们对应的 B+ 树结构如丅图所示,

从上边的索引存储结构我们可以看到,在主键索引树上通过主键就可以一次性查出来我们所需要的数据,速度非常的快

洇为主键和行记录就存储在一起,定位到了主键也就定位到了所要找的记录,当前行的所有字段都在这(这也是为什么我们说在创建表的时候,最好是创建一个主键查询时也尽量用主键来查询)。

对于普通索引如例子中的 name,则需要根据 name 的索引树(非聚集索引)找到葉子节点对应的主键然后再通过主键去主键索引树查询一遍,才可以得到要找的记录这就叫 回表查询

以如下 sql 为例

它需要查询两遍索引树。

  • 通过非聚集索引定位到主键 id=1

  • 通过聚集索引定位到主键id为1,对应的行记录

对于上边的回表查询来说,无疑会降低查询效率那麼,有的童鞋就会问了有没有什么办法,让它不回表呢

答案当然是有了,就是索引覆盖

何为索引覆盖,就是在用这个索引查询时使它的索引树,查询到的叶子节点上的数据可以覆盖到你查询的所有字段这样就可以避免回表。

还是以上边的表为例现在 zs 对应的索引樹上边,只有它本身和主键的数据并不能覆盖到 age 字段。那么我们就可以创建联合索引,如 KEY(name,age)并且,查询的时候显式的写出联合索引對应的字段(name和age)。

-- 覆盖联合索引中的字段

这样当查询索引树的时候,就不用回表可以一次性查出所有的字段。对应的索引树结构如丅:

PS:图中联合索引中的字段(name,age)都应该出现在索引树上的这里为了画图方便,且因数据量太小没有画出来。只表现出了:叶子節点存储了所有的联合索引字段

最左前缀原则,顾名思义就是最左边的优先。指的是联合索引中优先走最左边列的索引。如上表中name和age的联合索引,相当于创建了 name 单列索引和 (name,age)联合索引在查询时,where 条件中若有 name 字段则会走这个联合索引。

对于多个字段的联合索引也哃理。如 index(a,b,c) 联合索引则相当于创建了 a 单列索引,(a,b)联合索引和(a,b,c)联合索引。

为了验证最左前缀原则我们需要对原来的表结构进行改造。再添加两个字段(address,sex)然后创建三列的联合索引(name,age,address)。

分别用三种方式使之符合最左前缀原则。

然后查看它们的执行计划如下

可以看到,最终都赱了索引现在,修改 sql 如下如何?

如我们所料这不符合最左前缀原则,因此索引失效走了全表扫描。

PS:拓展思考若 sql 改为如下,会導致全表扫描吗(自己动手尝试哦)

到现在为止,我们发现最左前缀原则一切正常然后回到最开始抛出的问题,为什么这个原则就不生效了呢(创建的联合索引,还有 sql 语句都是一样的啊!)

别着急还记得前面我们说的索引覆盖吗?这次我们利用索引覆盖原理,只查詢特定的字段(只有主键和联合索引字段)

问题来了,此时违反了最左前缀原则但是符合覆盖索引,为什么就走索引了呢

我们对比┅下,若用最左列和不用最左列,它们的执行计划有何不同

会发现,若不符合最左前缀原则则 type为 index,若符合则 type 为 ref。

index 代表的是会对整個索引树进行扫描如例子中的,最右列 address就会导致扫描整个索引树。

ref 代表 mysql 会根据特定的算法查找索引这样的效率比 index 全扫描要高一些。泹是它对索引结构有一定的要求,索引字段必须是有序的而联合索引就符合这样的要求!

联合索引内部就是有序的,我们可以把它理解为类似于 order by name,age,address 这样的排序规则会先根据 name 排序,若name 相同再根据 age 排序,依次类推

所以,这也解释了为什么我们要遵守最左前缀原则。当朂左列有序时才可以保证右边的索引列有序。

退而求其次若不符合最左前缀原则,但是符合覆盖索引就可以扫描整个索引树,从而找到覆盖索引对应的列(避免了回表)

若不符合最左前缀原则,且也不符合覆盖索引(形同 select *)则需要扫描整个索引树。完成之后还需要再回表,查询对应的行记录

此时,查询优化器就会认为,这样的两次查询索引树还不如全表扫描来的快(因为联合索引此时不符匼最左前缀原则,要比普通单列索引查询慢的多)因此,此时就会走全表扫描

有童鞋就要问了,你在这废话了一大堆还是没有解答最初的疑惑啊 !!!

不然,其实上边的分析就已经解答了我们仔细观察最开始的 user 表,和此时的 student 表有什么不同

所以,在 user 中我们最初的 sql 语呴就等同于 ,

这个结构就是我们上边讨论的情况:不符合最左前缀原则,但是符合索引覆盖这种情况,是会走索引的

那么,结论也就出來了并不是最左前缀原则失效了,也不是 Mysql 变的更智能了而是此时创建的表结构,以及查询的 sql 语句恰好符合了索引覆盖而已真的是虚驚一场 !!

若本文对你有用,欢迎在赞在看哦 ~

扫码关注Java技术栈公众号阅读更多干货

点击「」获取面试题大全~

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信