✅什么是最左前缀匹配?为什么要遵守?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
简介: MySQL 的最左前缀匹配原则是指查询时利用索引的最左边列进行匹配。如果创建了组合索引 (col1, col2, col3),查询条件包括 col1、(col1, col2) 或全列时,MySQL 可以高效利用索引。反之,如果条件仅涉及 col2、col3 或 (col2, col3),则通常无法利用该索引。虽然查询条件顺序可变,但不涉及最左列时,无法使用索引。MySQL 8.0 引入了索引跳跃扫描,允许在某些情况下不遵循最左前缀匹配,提高查询效率。然而,是否使用取决于优化器的成本估算,并受特定条件限制。设计索引时,仍应优先考虑高区分度的字段。

在 MySQL 中,最左前缀匹配指的是在查询时利用索引的最左边部分进行匹配。当你执行查询时,如果查询条件涉及到组合索引的前几个列,MySQL 就能够利用该复合索引来进行匹配。

组合索引即由多个字段组成的联合索引,比如 idx_col1_col2_col3 (col1,col2,col3)。

假设我们创建了一个组合索引 (col1, col2, col3),如果查询条件是针对 col1、(col1, col2) 或者 (col1, col2, col3),那么 MySQL 就能利用该复合索引进行最左前缀匹配。

然而,如果查询条件只涉及到 col2、只涉及到 col3 或者只涉及到 col2 和 col3,也就是没有包含 col1,那么通常情况下(不考虑索引跳跃扫描等其他优化),就无法利用该索引进行最左前缀匹配。

值得注意的是,最左前缀匹配与查询条件的顺序无关。无论你写的是 where col1 = "Paidaxing" and col2 = "666" 还是 where col2 = "666" and col1 = "Paidaxing",对结果都没有影响,命中的结果仍然一样。

此外,需要大家注意的是,许多人可能会误以为创建一个组合索引 (col1, col2, col3) 时,数据库会创建三个索引 (col1)、(col1, col2) 和 (col1, col2, col3),这样的理解其实是不正确的。实际上,数据库只会创建一棵 B+树,只不过在这颗树中,首先按照 col1 进行排序,然后在 col1 相同时再按照 col2 排序,col2 相同再按照 col3 排序。

另外,如果没有涉及到联合索引,单个字段的索引也需要遵守最左前缀原则。即当一个字段的值为"abc"时,当我们使用 like 进行模糊匹配时,like "ab%" 是可以利用索引的,而 "%bc"则不行,因为后者不符合最左前缀匹配的原则。

为什么要遵循最左前缀匹配

我们都了解,在 MySQL 的 InnoDB 引擎中,索引是通过 B+树来实现的。不论是普通索引还是联合索引,都必须构建 B+树的索引结构。

针对普通索引,其存储结构是在 B+树的每个非叶子节点上记录索引的值,而在 B+树的叶子节点上,则记录了索引的值和聚簇索引(主键索引)的值。

如:
image.png

(这样图是简化的,实际上还有双向链表,具体的可以参考索引介绍的文章)

在联合索引中,比如联合索引 (age, name),同样也是构建了一棵 B+树。在这棵 B+树中,非叶子节点中记录的是 name 和 age 两个字段的值,而在叶子节点中记录的是 name、age 两个字段以及主键 id 的值。

image.png

在存储过程中,如上所述,当 age 不同时,按照 age 排序;当 age 相同时,则按照 name 排序。

因此,了解了索引的存储结构之后,我们就很容易理解最左前缀匹配了:由于索引底层是一棵 B+树,如果是联合索引的话,在构造 B+树时,会先按照左边的键进行排序,当左边的键相同时,再依次按照右边的键进行排序。

因此,在通过索引查询时,也需要遵守最左前缀匹配的原则,即需要从联合索引的最左边开始进行匹配。这就要求查询语句的 WHERE 条件中包含最左边的索引值。

MySQL 索引一定遵循最左前缀匹配吗?

因为索引底层是一个 B+树,如果是联合索引的话,在构造 B+树的过程中,会先按照左边的键进行排序。当左边的键相同时,再依次按照右边的键排序。

因此,在通过索引进行查询时,也需要遵守最左前缀匹配的原则,即需要从联合索引的最左边开始进行匹配。这就要求查询语句的 WHERE 条件中包含最左边的索引值。这就是最左前缀匹配的概念。

在 MySQL 之前的版本中,一直都是遵循最左前缀匹配的原则,这句话在以前是正确的,没有任何问题。但是在 MySQL 8.0 中,情况就有所不同了。因为在 8.0.13 中引入了索引跳跃扫描的特性。

补充知识

索引跳跃扫描

MySQL 8.0.13 版本引入了索引跳跃扫描(Index Skip Scan)优化,对于 range 查询提供了支持。即使不符合组合索引最左前缀原则的条件下,SQL 依然能够使用组合索引,从而减少不必要的扫描。

让我们通过一个例子来解释一下。首先,我们有下面这样一张表(参考了 MySQL 官网的例子,但经过了一些改动和优化):

CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL);
CREATE INDEX idx_t on t1(f1,f2);
INSERT INTO t1 VALUES
  (1,1), (1,2), (1,3), (1,4), (1,5),
  (2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;

通过以下 SQL 语句,先创建一张名为 t1 的表,并将字段 f1 和 f2 设置为联合索引。然后向其中插入一些记录。

接着,分别在 MySQL 5.7.9 和 MySQL 8.0.30 上执行EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 = 40;

image.png

可以看到,主要有以下几个区别:

MySQL 5.7 中,type = index,rows=160,extra=Using where;Using index

MySQL 8.0 中,type = range,rows=16,extra=Using where;Using index for skip scan

type 字段表示扫描方式,其中 range 表示范围扫描,而 index 表示索引树扫描。通常情况下,范围扫描要比索引树扫描快得多。

通过 rows 字段也能够观察到这一点,使用索引树扫描的方式共扫描了 160 行,而范围扫描方式只扫描了 16 行。

然后,关键在于为什么 MySQL 8.0 中的扫描方式更快呢?这主要是因为采用了"Using index for skip scan"的技术。

换句话说,尽管我们的 SQL 没有遵循最左前缀原则,仅仅使用了 f2 作为查询条件,但经过 MySQL 8.0 的优化,仍然通过索引跳跃扫描的方式利用了索引。

优化原理

那么他是怎么优化的呢?
在 MySQL 8.0.13 及以后的版本中,执行SELECT f1, f2 FROM t1 WHERE f2 = 40;的过程如下:

  1. 获取 f1 字段的第一个唯一值,即 f1=1。
  2. 构造条件f1=1 and f2=40,进行范围查询。
  3. 获取 f1 字段的第二个唯一值,即 f1=2。
  4. 构造条件f1=2 and f2=40,进行范围查询。
  5. 重复上述步骤,直到扫描完 f1 字段的所有唯一值。
  6. 最后将结果合并并返回。

换句话说,最终执行的 SQL 语句类似于下面的形式:

SELECT f1, f2 FROM t1 WHERE f1 =1 and f2 = 40
UNION
SELECT f1, f2 FROM t1 WHERE f1 =2 and f2 = 40;

即,MySQL 的优化器帮我们把联合索引中的 f1 字段作为查询条件进行查询了。

限制条件

在了解了索引跳跃扫描的执行过程后,一些聪明的读者可能会意识到,这种查询优化更适用于具有较少取值范围和低区分度的字段(比如性别),而当字段的区分度特别高时(比如出生年月日),这种查询可能会变得更慢。

因此,是否使用索引跳跃扫描,实际上取决于 MySQL 优化器经过成本预估后做出的决定。

通常情况下,这种优化技术适用于联合索引中第一个字段的区分度较低的情况。但需要注意的是,并非绝对如此。尽管一般情况下我们不太会将区分度较低的字段放在联合索引的左边,但 MySQL 提供了这样的优化方案,这说明确实存在这样的需求。

然而,我们不应该过度依赖这种优化。在建立索引时,仍然应优先考虑将区分度高且频繁查询的字段放置在联合索引的左边。

此外,在 MySQL 官网中还提到了索引跳跃扫描的其他一些限制条件:

  • 表 T 必须至少有一个联合索引,但对于联合索引(A,B,C,D),A 和 D 可以为空,但 B 和 C 必须非空。
  • 查询只能依赖于单张表,不能进行多表连接。
  • 查询中不能使用 GROUP BY 或 DISTINCT 语句。
  • 查询的字段必须是索引中的列。

好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
SQL 关系型数据库 MySQL
13. 知道什么是左前缀原则嘛 ?
MySQL的联合索引遵循左前缀原则,检索时从索引的最左侧字段开始匹配。例如,对`age`, `name`, `sex`创建的组合索引`index_age_name_sex`,相当于建立了`(age)`, `(age,name)`, `(age,name,sex)`三个独立索引。查询时,只有包含最左边字段的条件才会使用索引。例如:`WHERE age=49`和`WHERE age=49 AND name='Alice'`会使用索引,但`WHERE sex='man'`不会。查询优化器会自动调整WHERE子句顺序以利用合适索引。
23 0
|
2月前
|
JavaScript 前端开发 Java
|
2月前
|
JavaScript 前端开发 开发者
正则表达式深度解析:斜杠的妙用
【2月更文挑战第29天】
498 0
正则表达式深度解析:斜杠的妙用
|
2月前
|
JavaScript 前端开发 Java
|
2月前
如何去掉字符串中文括号及其内部的内容三种方式
如何去掉字符串中文括号及其内部的内容三种方式
26 0
|
2月前
|
算法 测试技术 C#
【字典树】【字符串】【 前缀】100268. 最长公共后缀查询
【字典树】【字符串】【 前缀】100268. 最长公共后缀查询
|
2月前
正则表达前一个元素至少出现一次
正则表达前一个元素至少出现一次
67 4
|
10月前
|
开发者 Perl
正则表达式中的模式修正符S和M 妙用!
教你如何正确使用正则表达式中的模式修正符S和M!
46 0
正则表达式中的模式修正符S和M 妙用!
|
12月前
蓝桥——括号合法组合 2020-11-26
蓝桥——括号合法组合 2020-11-26
|
12月前
|
数据库 索引
左前缀原则
左前缀原则(Left-Prefix Principle)是数据库索引设计中的一个重要原则,它指出在创建索引时,应该优先考虑最常用的查询,并使用最左边的列作为索引的前缀。下面我将详细介绍左前缀原则的定义、作用以及实际应用场景。
89 0