好的,我们来深入浅出地讲解 MySQL 中最重要、最核心的索引使用原则之一:联合索引的最左匹配原则。
![图片[1]_MySQL联合索引最左匹配原则_知途无界](https://zhituwujie.com/wp-content/uploads/2026/01/d2b5ca33bd20260104093342.png)
一、什么是联合索引?
联合索引(Compound Index / Composite Index),也叫复合索引,是指一个索引包含多个列(字段)。
例如,我们有一个用户表 users,经常需要根据 last_name 和 first_name 来查询用户,那么我们可以创建一个联合索引:
CREATE INDEX idx_name ON users(last_name, first_name);
这个 idx_name 就是一个包含了 last_name 和 first_name 两个字段的联合索引。
二、什么是最左匹配原则?
最左匹配原则(Leftmost Prefix Principle)是 MySQL 建立和使用联合索引的“潜规则”。
它的核心思想是:MySQL 的联合索引在查询时,会从索引定义的最左边的列开始匹配,并且不会跳过中间的列。只有当索引中的前缀列(从左开始的连续列)被用于查询条件时,索引才会被有效地使用。
可以把联合索引想象成一本书的目录,目录是按“章 -> 节 -> 小节”的顺序编排的。如果你想查找某个小节的内容,你必须先从“章”开始找,然后是“节”,最后才能定位到“小节”。你不能跳过“章”或“节”直接去找“小节”,那样目录就失效了。
三、原理剖析:索引是如何存储的?
要理解最左匹配,首先要知道联合索引在底层 B+Tree 数据结构中的存储方式。
对于索引 INDEX (last_name, first_name),MySQL 会先按 last_name 进行排序,在 last_name 相同的情况下,再按 first_name 排序。
简化的索引树结构示意:
| last_name | first_name | …其他列… |
|---|---|---|
| Adams | Alice | … |
| Adams | Bob | … |
| Adams | Charlie | … |
| Baker | David | … |
| Baker | Eve | … |
| Clark | Frank | … |
关键点:
- 整棵树首先按
last_name排序。所有last_name='Adams'的记录会聚集在一起。 - 在
last_name='Adams'这个分组内,再按first_name排序(Alice,Bob,Charlie)。
正是因为这种“先按第一列排序,再按第二列排序”的结构,决定了查询必须从最左列开始使用。
四、实战演练:哪些查询能用上索引?
假设我们有索引 idx_name (last_name, first_name)。
✅ 情况1:查询条件包含最左列(全值匹配)
-- 会使用索引,并且是最高效的使用方式
SELECT * FROM users
WHERE last_name = 'Adams' AND first_name = 'Alice';
分析:查询从最左列 last_name 开始匹配,并且连续使用了索引的两列。MySQL 可以快速定位到 last_name='Adams' 的分支,然后在该分支下精确找到 first_name='Alice' 的记录。
✅ 情况2:只查询最左列(前缀匹配)
-- 会使用索引,但只用到索引的第一列
SELECT * FROM users
WHERE last_name = 'Adams';
分析:查询条件正好从索引的最左列开始,并且没有跳过它。MySQL 可以使用索引快速定位所有 last_name='Adams' 的记录。虽然没用到 first_name,但索引依然有效。
✅ 情况3:查询条件包含最左列及中间列(范围匹配)
-- 会使用索引,但只用到索引的前两列(到范围查询列为止)
SELECT * FROM users
WHERE last_name = 'Adams' AND first_name LIKE 'A%';
分析:last_name 是等值查询,first_name 是范围查询。MySQL 会使用索引找到所有 last_name='Adams' 的记录,然后在这些记录中,利用索引继续查找 first_name 以 ‘A’ 开头的记录。注意:在范围查询之后的列,索引就失效了。
❌ 情况4:跳过最左列(失效!)
-- 不会使用索引!全表扫描
SELECT * FROM users
WHERE first_name = 'Alice';
分析:查询条件直接从第二列 first_name 开始。在索引树中,first_name 的值是无序的(只有在 last_name 相同的组内才有顺序)。因此,MySQL 无法通过索引直接定位到 first_name='Alice' 的记录,只能进行全表扫描。
❌ 情况5:最左列条件为范围查询,后面列条件无效(部分失效)
-- 会使用索引,但只用到第一列(last_name)
SELECT * FROM users
WHERE last_name LIKE 'A%' AND first_name = 'Alice';
分析:last_name 使用了范围查询(LIKE 'A%'),这会导致 MySQL 无法再利用索引对 first_name 进行精确查找。索引只被用于筛选 last_name 以 ‘A’ 开头的记录,对于 first_name 的条件,需要回表后在数据行中进行过滤。
❌ 情况6:顺序颠倒(不一定失效,但优化器会调整)
-- 现代MySQL优化器会自动优化WHERE条件顺序
-- 所以这个查询依然会使用索引,效果和情况1一样
SELECT * FROM users
WHERE first_name = 'Alice' AND last_name = 'Adams';
分析:虽然 SQL 语句中 first_name 写在了前面,但 MySQL 的查询优化器足够智能,它会识别出 last_name 是索引的最左列,从而自动调整查询顺序来使用索引。但是,依赖优化器是不好的习惯,写 SQL 时应尽量遵循最左匹配顺序。
五、如何利用最左匹配原则优化索引?
- 选择合适的索引列顺序
- 高频查询列在前:将 WHERE 子句中最常作为查询条件的列放在最左边。
- 高选择性列在前:选择性(Cardinality)高的列(即列中唯一值多的列)放在前面,能更快地缩小查询范围。例如,
gender(男/女)选择性低,email选择性高,通常email更适合放在前面。 - 兼顾排序和分组:如果查询中经常有
ORDER BY或GROUP BY,可以考虑将排序列加入索引,并放在合适的位置。例如,INDEX (last_name, first_name)可以优化ORDER BY last_name, first_name。
- 覆盖索引(Covering Index)
如果索引包含了查询所需要的所有字段,MySQL 就无需回表查询数据行,效率极高。-- 假设索引为 INDEX (last_name, first_name, email) SELECT last_name, first_name, email FROM users WHERE last_name = 'Adams';这个查询只需要last_name,first_name,email三个字段,它们都在索引idx_name中,所以 MySQL 只需扫描索引树即可完成查询,速度极快。
总结
| 查询条件 | 是否使用索引 | 说明 |
|---|---|---|
WHERE last_name = ? | ✅ | 使用索引第一列 |
WHERE last_name = ? AND first_name = ? | ✅ | 使用索引全部列 |
WHERE last_name = ? AND first_name LIKE 'A%' | ✅ | 使用索引前两列 |
WHERE first_name = ? | ❌ | 跳过最左列,索引失效 |
WHERE last_name LIKE 'A%' AND first_name = ? | ⚠️ | 只使用索引第一列 |
WHERE first_name = ? AND last_name = ? | ✅ | 优化器会调整顺序,但建议按索引顺序写 |
牢记口诀:带头大哥不能死,中间兄弟不能断。 只要你的查询条件从联合索引的第一列开始,并且中间没有跳过列,索引就会为你所用。
























暂无评论内容