深入理解MySQL 最左前缀法则

 更新时间:2026年06月21日 09:23:17   作者:花生了什么事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 排。

最左前缀法则

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

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

  • 索引是 (a, b, c) 三个列
  • 查询条件必须从 a 开始,然后是 b,然后是 c,中间不能跳过

来看具体场景:

查询条件是否命中索引原因
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 最左前缀法则内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 防止mysql重复插入记录的方法

    防止mysql重复插入记录的方法

    这篇文章主要为大家详细介绍了防止mysql重复插入记录的方法,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • /var/log/pacct文件导致MySQL启动失败的案例分享

    /var/log/pacct文件导致MySQL启动失败的案例分享

    这篇文章主要介绍了/var/log/pacct文件导致MySQL启动失败的案例分享,这是个比较让人郁闷的问题,找不到MySQL启动失败的原因进可以按此文的方法试一试,需要的朋友可以参考下
    2015-01-01
  • mysql跨数据库复制表(在同一IP地址中)示例

    mysql跨数据库复制表(在同一IP地址中)示例

    这篇文章主要介绍了mysql跨数据库复制表(在同一IP地址中)示例,需要的朋友可以参考下
    2014-03-03
  • mysql约束和高级sql详解

    mysql约束和高级sql详解

    文章介绍了MySQL中的约束类型(如主键、外键、唯一约束、非空约束、检查约束、默认值和自增)以及高级SQL功能(如子查询、连接、事务和存储过程),感兴趣的朋友一起看看吧
    2024-11-11
  • MySQL压缩表创建方法示例详解

    MySQL压缩表创建方法示例详解

    本文给大家介绍MySQL压缩表创建方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-09-09
  • MySQL 5.0.96 for Windows x86 32位绿色精简版安装教程

    MySQL 5.0.96 for Windows x86 32位绿色精简版安装教程

    这篇文章主要介绍了MySQL 5.0.96 for Windows x86 32位绿色精简版安装教程,需要的朋友可以参考下
    2017-10-10
  • mysql存储过程之循环语句(WHILE,REPEAT和LOOP)用法分析

    mysql存储过程之循环语句(WHILE,REPEAT和LOOP)用法分析

    这篇文章主要介绍了mysql存储过程之循环语句(WHILE,REPEAT和LOOP)用法,结合实例形式分析了mysql存储过程循环语句WHILE,REPEAT和LOOP的原理、用法及相关操作注意事项,需要的朋友可以参考下
    2019-12-12
  • MySQL 如何处理隐式默认值

    MySQL 如何处理隐式默认值

    这篇文章主要介绍了MySQL 处理隐式默认值的相关资料,帮助大家更好的理解和使用MySQL,感兴趣的朋友可以了解下
    2020-12-12
  • Mysql查询去空格的多种方法汇总

    Mysql查询去空格的多种方法汇总

    SQL查询语句中空格是用来分隔关键字、表名、列名等的,然而空格也会影响查询效率,因为查询语句中的空格越多,查询的速度就越慢,下面这篇文章主要给大家介绍了关于Mysql查询去空格的多种方法,需要的朋友可以参考下
    2023-04-04
  • mysql now()函数调用系统时间不对的解决方法

    mysql now()函数调用系统时间不对的解决方法

    mysql的now()函数与实际时间不符,本文就详细的介绍一下mysql now()函数调用系统时间不对的解决方法,非常具有实用价值,需要的朋友可以参考下
    2023-05-05

最新评论