最左匹配原则:复合索引 (a,b,c) 在 a=? AND b>? AND c=? 查询下的使用分析
查询条件是:WHERE a = ? AND b > ? AND c = ?
,当我们查询时,一定能走到索引吗?有几个字段能走到索引?
直接回答:
- 能走到索引吗? 能,这个查询一定会使用
(a, b, c)
索引。 - 走到索引的有几个? 精确地说,索引中的 前两个字段(a 和 b) 被用于“查找”(Range),第三个字段 c 被用于“过滤”(Filtering)。
下面我来详细解释为什么,这对于你理解复合索引的工作原理非常重要。
详细原理分析
MySQL 的 InnoDB 引擎使用 B+树 结构存储索引。对于复合索引 (a, b, c)
,它的排序方式是:
- 首先按
a
字段排序。 - 在
a
相同的情况下,按b
字段排序。 - 在
a
和b
都相同的情况下,才按c
字段排序。
你的查询条件是:WHERE a = ? AND b > ? AND c = ?
索引的查找过程可以分解为三个层面:Index Key, Index Filter, 和 Table Filter。
1. Index Key (索引查找 - 用于确定扫描范围)
这是索引最核心的作用。MySQL 会利用索引的最左前缀原则来定位到需要扫描的数据范围。
a = ?
:这是一个等值查询。引擎能利用索引快速定位到所有a
等于特定值的叶子节点区域。这是第一层精确筛选。b > ?
:这是一个范围查询。在a
已经固定的前提下,索引中的数据是按b
排序的。因此,引擎可以在这个a
固定的区域内,快速找到b
大于某个值的起始点,然后从这个起始点开始向后扫描,直到这个a
区域的结束点。
到这一步为止,索引 (a, b)
的部分被完美地用于快速定位和扫描。 这就是为什么我们说索引“走了”两个字段。
2. Index Filter (索引过滤 - 在扫描范围内筛选)
现在,引擎已经在扫描所有满足 a = ? AND b > ?
的记录索引项了。但是我们的查询还有第三个条件 c = ?
。
- 虽然
c
也在索引中,但由于b
是范围查询,导致c
的相对顺序在索引中失效了。也就是说,在b > ?
的范围内,c
的值并不是有序的。 - 因此,MySQL 无法再使用索引树来快速定位
c = ?
的记录(即无法对c
进行“查找”)。 - 但是,InnoDB 仍然可以做 “索引条件下推”(Index Condition Pushdown, ICP)。这意味着,引擎会在读取索引页的时候,就顺便判断一下这条索引记录中的
c
字段是否等于?
。如果不等于,则直接跳过,根本不需要回表。
所以,字段 c
也被用到了,但它不是在“查找”层面,而是在“过滤”层面。 它的作用是极大减少了需要回表查询主键的数据行数。
3. Table Filter (表过滤 - 回表后筛选)
如果查询还包含了 SELECT *
或者表中其他不在索引中的字段,那么对于通过 Index Filter 筛选后的记录,引擎还需要通过主键回表去读取完整的数据行。
如果表中还有其他查询条件(例如 AND d = ?
),而 d
不在索引中,那么这些条件会在回表读取到完整数据行之后再进行过滤。
在你的例子中,由于 WHERE
条件已经结束,通常到 Index Filter 就完成了。
总结与可视化
假设我们有一个 (a, b, c)
索引,数据如下:
a | b | c |
---|---|---|
1 | 1 | 5 |
1 | 2 | 4 |
1 | 3 | 3 |
1 | 4 | 4 |
2 | 1 | 1 |
查询 a=1 AND b>1 AND c=4
:
- Index Key (查找):通过索引树快速定位到
a=1
的子树,然后在该子树内找到b>1
的起始点(即(1,2,4)
这条记录)。从此开始向后扫描,直到a != 1
。 - Index Filter (过滤):在扫描
(1,2,4)
,(1,3,3)
,(1,4,4)
这些索引项时,直接检查索引中的c
值。(1,2,4)
的c=4
,符合;(1,3,3)
的c=3
,不符合,跳过;(1,4,4)
的c=4
,符合。 - 结果:最终只需要为
(1,2,4)
和(1,4,4)
这两条索引记录回表(如果需要的话)。
结论
字段 | 索引使用方式 |
---|---|
a | 等值查找 (Index Key) |
b | 范围查找 (Index Key) |
c | 索引过滤 (Index Filter,利用ICP) |
所以,这个索引 (a, b, c)
对本次查询是有效的,三个字段都发挥了作用。但通常我们说“走了几个字段的索引”指的是用于“查找”(Index Key)的字段,所以严格来说是 2个(a和b)。
优化建议:
如果 c
的选择性非常高(即几乎每条记录的 c
值都不同),而 b
的范围很大,你可以考虑建立一个 (a, c, b)
的索引。这样查询条件变成 a = ? AND c = ? AND b > ?
,所有三个字段都可以用于等值查找和范围查找,效率可能更高。但这需要结合你的具体数据分布来判断。