跳至主要內容

联合索引与最左前缀匹配

AruNi_Lu数据库MySQL约 1598 字大约 5 分钟

本文内容

前言

在谈及到索引失效时,往往都会想到查询的条件是否满足最左前缀匹配。同时,面试也经常会问有关最左前缀匹配的 SQL,问你到底有没有走索引。所以把最左前缀匹配弄清楚是十分有必要的。

1. 联合索引是如何存放的

因为最左前缀匹配原则主要是与联合索引有关。所以我们先来看看联合索引是如何存放在 B+ 树中的。

我们知道,InnoDB 中非聚簇索引的叶子节点是不存放完整记录的,它只存放 索引列 + 主键。那么如果我有多个索引列(即联合索引),它会怎么样帮我存放这多个列呢?

假设我们有个表,id 为主键,有一个联合索引(name, age),那么这颗 B+ 树的叶子节点长这样(非叶子节点就只有索引列):

image-20230219202539043

可以发现,联合索引(name, age)的排序规则是 先按照最左侧的 name 排序,name 相同才按照 age 排序

2. 最左前缀匹配原则是什么

正是因为联合索引的排序规则,所以才会存在 最左前缀匹配原则,顾名思义,即 优先按照最左列进行索引的匹配

还是用上面的例子,如果我的查询条件是 where age = 16,就无法使用到索引(不考虑索引覆盖),因为不满足最左前缀匹配原则(只有 name 相等时,age 才有序,而只用 age 查询,age 在全局上是无序的)。

不过需要注意,联合索引出现的位置无关紧要,因为优化器会做优化,例如 where age = 14 and name = 小C 也是可以使用到索引的。

还有一种情况 where name like '小%',也称得上是满足最左前缀匹配,也能用上索引,查找到第一个符合 开头的记录,然后向后遍历,直到不满足为止。

可以发现,最左前缀匹配分为两种情况:

  • 联合索引的最左 N 个字段
  • 字符串索引的最左 M 个字符

3. 如何安排联合索引中列的顺序

由于最左前缀匹配原则的存在,联合索引中列的顺序就显得尤其重要了,那我们在建立联合索引的时候,该如何决策联合索引中列的顺序呢?

一、索引的复用能力

由于可以满足最左前缀匹配,所以当有了 (a, b) 这个联合索引后,一般就无需再单独给 a 建立索引了(单独使用 a 字段查询是可以使用到联合索引的)。

所以第一原则是:如果通过调整联合索引的顺序,可以少维护一个索引,那么这个联合索引往往就是需要优先考虑采用的

二、空间的考虑

如果有时候,我们既需要 (a, b) 这个联合索引,通过又有基于 a、b 各自的查询呢?查询条件里只有 b 是无法使用联合索引的。

这个时候我们可以有两个方案:

  • 方案一:联合索引 (a, b),单列索引 (b);
  • 方案二:联合索引 (b, a),单列索引 (a)。

这两个方案到底选哪个呢?这时我们就要 考虑空间 了。如果 a 字段占用的字节数比 b 大,那么肯定选择方案一嘛,这样单列索引 (b) 占用的空间更小,一页中能存储的记录自然也就更多。

三、索引区分度的考虑

索引区分度,即某个字段不同值的数量「÷」表的总行数,公式如下:

image-20230219211559913

所以,查看某个字段的区分度可以使用如下 SQL:

  • SELECT COUNT(DISTINCE LEFT(column_name, length)) / COUNT(*) FROM table_name;

  • 其中:LEFT 函数是取 column_name 这个字段的前 length 个字符;

所以,在建立联合索引的时候,应该尽量把区分度大的字段放在前面,因为区分度大的字段不重复的数据多,那么每次过滤掉的数据就多,使得查询效率更高。

4. 什么情况下联合索引会失效

通过对最左前缀匹配的理解,我们可以得出几个简单的索引失效的情况。例如联合索引 (a, b, c),那么索引失效的情况可能有:

  • where b = 1
  • where c = 2
  • where b = 3 and c = 4

这是比较容易判断的,那只要使用了联合索引的最左 N 个字段,就一定会全都走索引吗?

显然不是,例如下面的一种情况:

  • SQL 语句:select * from table where a > 1 and b = 3;
  • 联合索引 (a, b)。

这句 SQL 语句中,字段 a,b 都使用到了联合索引吗?

首先可以确定一点,字段 a 是肯定使用到了索引的,因为它满足最左前缀匹配,能形成有序的扫描区间 (1, +∞),只需要找到第一条 a > 1 的记录,然后向后查找即可。

那么 b 有没有使用到索引,就要看看它是否有序。我们知道只有当 a 相等时,b 才有序,而在 a > 1 的记录中,b 是无序的,所以 b 使用不到索引

那如果 SQL 语句是 select * from table where a >= 1 and b = 3;,此时 b 字段也就能使用到索引了,因为 当 a = 1 时,b 字段是有序的

所以同理,where a between 1 and 5 and b = 3; 也是能使用到索引的,因为 between 是包含两边的边界值的

所以,判断某条查询语句是否走了索引,关键就看它是否能形成有序区间,能就可以走索引

5. 参考文章

上次编辑于: