Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL 最左前缀法则

深入理解MySQL 最左前缀法则

作者:花生了什么事o

本文解释了最左前缀原则的本质是 B+ 树排序规则的直接推论,通过分析联合索引的存储结构,帮助读者理解为什么跳过前面的列会导致索引失效,感兴趣的朋友可以参考下

联合索引是什么

联合索引就是多个列组合成一个索引。

ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);

这条语句创建了一个联合索引,包含三个列:user_idstatuscreate_time

和单列索引的区别在哪?单列索引是对一个列建索引,联合索引是对多个列的组合建索引。 你可以理解为,联合索引是一本按"user_id + status + create_time"顺序排列的字典,先按 user_id 排,user_id 相同的再按 status 排,status 也相同的再按 create_time 排。

最左前缀法则

最左前缀原则可以总结为:查询条件必须从索引的最左列开始连续使用,索引才能生效。

听起来有点抽象,我们拆解一下:

来看具体场景:

查询条件是否命中索引原因
WHERE a = 1命中 a从最左列开始
WHERE a = 1 AND b = 2命中 a, b连续使用
WHERE a = 1 AND b = 2 AND c = 3命中全部完整使用
WHERE b = 2不命中跳过了 a
WHERE b = 2 AND c = 3不命中跳过了 a
WHERE a = 1 AND c = 3只命中 a跳过了 b

最后一个有点特殊:WHERE a = 1 AND c = 3。MySQL 会用 a 来定位索引范围,但 c 没法用,因为 b 被跳过了,c 在索引中的位置不确定。

从 B+ 树结构理解为什么

要真正理解最左前缀,得看 B+ 树的结构。

假设我们有一个联合索引 (a, b, c),数据在 B+ 树中是这样排列的:

根节点
    │
    ├── [a=1, b=1, c=1]
    ├── [a=1, b=2, c=3]
    ├── [a=2, b=1, c=5]
    ├── [a=2, b=1, c=7]
    └── [a=3, b=2, c=1]

注意数据的排序规则:先按 a 排序,a 相同再按 b 排序,b 也相同再按 c 排序。

这意味着:

  1. WHERE a = 1 能走索引:因为 a 相同的数据在 B+ 树中是相邻的,可以快速定位
  2. WHERE a = 1 AND b = 2 能走索引:a 确定后,b 相同的数据也是相邻的
  3. WHERE b = 2 不能走索引:b 的值在不同 a 之间是分散的,没有顺序性,没法用 B+ 树的二分查找

索引的排序规则决定了只有从最左列开始连续匹配,才能利用 B+ 树的有序性。

你有一本按"省份-城市-区县"排序的通讯录。找"陕西省西安市未央区"很容易,找"陕西省未央区"也行(先定位陕西省,再跳过城市直接找区县——但效率会降低)。但如果只给你"未央区"三个字,你根本没法翻这本通讯录,因为未央区的数据分散在不同省份下面。

哪些情况会失效

最左前缀只是索引失效的其中一种情况。还有几种常见坑:

1. 范围查询右边的列失效

-- 索引 (a, b, c)

-- 只命中 a,b 和 c 失效
WHERE a = 1 AND b > 5 AND c = 3

b 用了范围查询(><BETWEEN),c 就没法用索引了。因为 b 的范围确定后,c 的值在范围内是无序的。

2. 函数操作导致失效

-- 索引 (user_id)

-- 不走索引 
SELECT * FROM orders WHERE YEAR(create_time) = 2025;

-- 走索引 
SELECT * FROM orders WHERE create_time > '2025-01-01' AND create_time < '2025-12-31';

对索引列做函数操作,MySQL 无法使用索引的有序性。改成范围查询就能走索引。

3. 隐式类型转换

-- 索引 (phone)

-- 不走索引 (phone 是 varchar,传入了 int)
SELECT * FROM user WHERE phone = 13800138000;

-- 走索引 
SELECT * FROM user WHERE phone = '13800138000';

类型不匹配时 MySQL 会做隐式转换,相当于对索引列用了函数,索引失效。

4. LIKE 左模糊

-- 索引 (name)

-- 不走索引 
SELECT * FROM user WHERE name LIKE '%张';

-- 走索引 
SELECT * FROM user WHERE name LIKE '张%';

左模糊查询无法利用 B+ 树的有序性,只能全表扫描。

5. OR 条件(部分场景)

-- 索引 (a), (b)

-- 不走索引
SELECT * FROM t WHERE a = 1 OR b = 2;

-- 走索引 (MySQL 8.0+ 的 Index Merge)
SELECT * FROM t WHERE a = 1 OR a = 2;

如果 OR 两侧的条件涉及不同索引,早期 MySQL 只能走全表扫描。MySQL 8.0 引入了 Index Merge 优化,可以同时使用多个索引再合并结果。

索引设计的实操建议

理解了原理,设计索引时记住这几条:

1. 等值查询的列放前面

-- 查询: WHERE user_id = 1 AND status = 'paid' AND create_time > '2025-01-01'

-- 好的索引
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);

-- 糟糕的索引(范围查询在前,后面的列失效)
ALTER TABLE orders ADD INDEX idx_time_user_status (create_time, user_id, status);

2. 区分度高的列放前面

-- status 只有几种值,区分度低
-- user_id 每个用户都不同,区分度高

-- 好:user_id 放前面
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);

-- 糟糕:status 放前面
ALTER TABLE orders ADD INDEX idx_status_user (status, user_id);

区分度公式:COUNT(DISTINCT column) / COUNT(*)。区分度越高,索引过滤能力越强。

3. 避免创建冗余索引

-- 已有索引 (a, b, c)
-- 不需要再建 (a, b),因为 (a, b, c) 的前缀已经覆盖了 (a, b)

-- 但可以考虑建 (a, b),然后删掉 (a, b, c)(如果 c 确实用不到的话)

可以用 sys.schema_redundant_indexes 视图查找冗余索引。

小结

最左前缀原则是 B+ 树排序规则的直接推论。索引按 (a, b, c) 排序,那就只有从 a 开始连续匹配,才能利用有序性进行二分查找。跳过前面的列,后面的列在数据分布上就是无序的,索引就会失效。

从设计角度看,最左前缀法则的本质是:索引的列顺序决定了哪些查询能受益。 这不是"怎么用索引"的问题,而是"怎么设计索引"的问题。把最常用的查询条件列放在最前面,把区分度高的列优先排列,才能让索引真正发挥作用。

到此这篇关于深入理解MySQL 最左前缀法则的文章就介绍到这了,更多相关MySQL 最左前缀法则内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文