一文系统梳理MySQL数据库索引失效的七个场景

 更新时间:2026年06月25日 09:25:52   作者:花生了什么事o  
本文系统梳理了 MySQL 索引失效的七种常见场景,从函数运算、隐式类型转换到优化器主动放弃索引,给出失败示例和原因,帮助读者理解和避免出现索引失效情况

明明建了索引,为什么还是慢

在开发环境跑一条查询,发现是毫秒级返回。上线之后,同样的查询,用户反馈页面转圈要好几秒。你打开 EXPLAIN 一看,type: ALL,全表扫描。明明在 create_time 上建了索引,为什么没有用上?

这类问题的根源,往往不是索引坏了,而是写法触发了某种"索引失效"的条件。MySQL 的优化器在选择执行计划时,会判断当前查询能否利用索引的有序性。如果不能,它宁可走全表扫描,也不会用一个没有用的索引。

这篇文章将把常见的失效场景做一次系统性的梳理,每种都给出原因和改法,让你在遇到慢查询时能快速定位问题。

索引失效的本质

在逐个看场景之前,先搞清楚一件事:索引为什么会失效?

索引的本质是一棵 B+ 树,它的价值在于有序性。二分查找之所以快,是因为数据排好了序。索引失效就是:写法破坏了 B+ 树有序性的利用条件,MySQL 无法通过索引快速定位数据。

索引就像一本按拼音排序的字典。你可以用拼音快速找到"张"字在哪一页。但如果你的查询是"找所有姓名中包含’张’字的人",这本字典就帮不了你了,因为"包含张"这个条件无法利用拼音的排序规则,你只能一页一页翻。

索引只在能利用有序性的查询中生效。一旦查询条件破坏了有序性的利用方式,索引就会失效。

场景一:对索引列做函数运算

这是最常见、也最容易犯的索引失效场景。

-- 索引:idx_create_time(create_time)

-- 失效:对索引列用了 YEAR() 函数
SELECT * FROM orders WHERE YEAR(create_time) = 2025;

-- 生效:改成范围查询
SELECT * FROM orders WHERE create_time >= '2025-01-01' AND create_time < '2026-01-01';

第一条查询为什么失效?因为 YEAR(create_time) 的结果是计算出来的,MySQL 需要对表中的每一行都执行一次 YEAR() 函数,然后拿结果去比较。这时候 B+ 树里存的是 create_time 的原始值,不是 YEAR() 的计算结果,有序性对计算后的值没有意义。

场景二:隐式类型转换

-- 索引:idx_phone(phone),phone 列是 VARCHAR 类型

-- 失效:传入了数字
SELECT * FROM user WHERE phone = 13800138000;

-- 生效:传入字符串
SELECT * FROM user WHERE phone = '13800138000';

phone 是 VARCHAR,你传了一个 INT。MySQL 的规则是:当字符串和数字比较时,MySQL 会把字符串转成数字。 这意味着对 phone 列的每一行,MySQL 都要先把它从字符串转成数字,再和 13800138000 比较。这等价于对索引列做了 CAST(phone AS SIGNED) 函数运算,和场景一相同,索引就会失效。

那如果反过来呢?

-- id 列是 INT 类型
-- 传入字符串也不会失效,因为 INT 和字符串比较时,MySQL 把字符串转成数字
-- 转换发生在常量一侧,不影响索引列
SELECT * FROM user WHERE id = '123';  -- 走索引

规则:字符串列传数字会失效,数字列传字符串不会失效。 因为只有当转换发生在索引列一侧时,才相当于对索引列做了函数运算。

怎么判断是否发生了隐式类型转换?用 EXPLAIN 看,如果 Extra 里出现了 Using wheretypeALL,那么大概率就是。

场景三:隐式字符编码转换

这个场景比类型转换更隐蔽,很多人踩过坑但不知道原因。

-- 库 A:字符集 utf8mb4,库 B:字符集 utf8

SELECT * FROM db_a.user a
JOIN db_b.order b ON a.name = b.user_name
WHERE a.name = '张三';

如果两张表的关联列字符集不同,MySQL 在做 JOIN 比较时需要做字符集转换。转换发生在索引列上时,等价于对索引列做了函数运算,索引失效。原理和场景一相同,只是"变形"的方式从类型转换变成了编码转换。

场景四:LIKE 左模糊匹配

-- 索引:idx_name(name)

-- 走索引:右模糊
SELECT * FROM user WHERE name LIKE '张%';

-- 不走索引:左模糊
SELECT * FROM user WHERE name LIKE '%三';

-- 不走索引:全模糊
SELECT * FROM user WHERE name LIKE '%三%';

回到字典的类比。字典按拼音排序,你可以快速找到"zhang"开头的所有字。但如果要求找"名字里包含’三’的字",拼音排序就帮不了你了,因为"三"可能出现在名字的任何位置,它的拼音分布在字典的各个角落。

LIKE '张%' 能走索引,是因为前缀确定时,这些值在 B+ 树中是连续的,可以范围扫描。LIKE '%三' 不行,是因为前缀不确定,值在树中是分散的,只能逐行检查。

场景五:OR 连接不同索引列

-- 索引:idx_a(a),idx_b(b)

-- 失效:OR 连接了不同索引的列
SELECT * FROM t WHERE a = 1 OR b = 2;

a = 1 可以走 idx_ab = 2 可以走 idx_b,但两条路径的结果要合并去重。MySQL 在大多数情况下只能选一个索引或者直接全表扫描。MySQL 8.0 引入了 Index Merge 优化,某些 OR 场景下可以同时使用多个索引,但优化器也可能认为全表扫描更快而放弃,不是万能的。

场景六:违反最左前缀原则

联合索引 (a, b, c) 的排序规则是先按 a 排,a 相同再按 b 排,b 相同再按 c 排。这个话题在我的另一篇文章《MySQL 最左前缀法则:为什么你的联合索引总是不生效》里详细展开过,这里只列核心规则,不做重复展开。

-- 索引:idx_abc(a, b, c)

WHERE b = 2;                  -- 跳过了 a,不走索引
WHERE b = 2 AND c = 3;        -- 跳过了 a,不走索引
WHERE a = 1 AND c = 3;        -- 跳过了 b,只用到 a
WHERE a = 1 AND b > 5 AND c = 3;  -- b 范围之后 c 无序,c 失效

原因和前面的场景一样:跳过最左列后,后续列在 B+ 树中是分散的,无法利用有序性。

场景七:数据量太小,优化器主动放弃

这个场景经常让人困惑:明明写法没问题,索引也建了,为什么 EXPLAIN 显示全表扫描?

-- 表里只有 100 条数据
EXPLAIN SELECT * FROM user WHERE age > 20;
-- type: ALL,没走索引

MySQL 的优化器是基于成本做决策的。走索引不是免费的,需要从 B+ 树根节点遍历到叶子节点,再通过主键回表取完整行数据。当表的数据量很小时,全表扫描可能只需要读一两个数据页,走索引反而需要更多 I/O。优化器经过成本估算,发现全表扫描更快,就会主动放弃索引。

同样的道理,当索引列的区分度太低时(比如 gender 只有 ‘M’ 和 ‘F’),走索引要回表的行数接近全表行数,顺序 I/O 比随机 I/O 快得多,优化器也会选择全表扫描。

这不是 bug,而是合理的优化。 可以用 FORCE INDEX 强制走索引来验证:如果强制走索引反而更慢,说明优化器的选择是对的。

一个公式:索引为什么会失效

看完七个场景,你会发现它们背后有一个共同的逻辑:

当 MySQL 无法利用 B+ 树的有序性来减少扫描行数时,索引就会失效。

拆开来看,有三种情况会导致有序性无法被利用:

情况本质对应场景
索引列被"变形"了原始值的有序性对变形后的值无意义函数运算、类型转换、字符集转换
查询条件无法利用排序模糊匹配无法定位到连续区间LIKE 左模糊、OR 跨索引
走索引的成本太高优化器认为全表扫描更划算数据量小、区分度低

遇到索引失效的问题,先用 EXPLAIN 确认是否走了索引,再对照这个表格判断属于哪种情况。大多数时候,我们的改法都是围绕一个原则:让索引列保持"原样",让查询条件能利用 B+ 树的有序性。

小结

索引失效不是 MySQL 出了问题,而是 B+ 树有序性的自然推论。索引之所以快,是因为数据排好了序,可以二分查找。一旦你的查询写法让 MySQL 没法利用这个排序,索引就等于没有用处了。

七个场景中,最值得记住的是两个:对索引列做函数/运算隐式类型转换。这两个在实际开发中出现频率最高,常容易被忽略。

排查索引失效时,EXPLAIN 是第一工具。看 type 是不是 ALL,看 key 是不是 NULL,看 Extra 里有没有 Using where。确认失效之后,对照本文提到的七个场景逐一排查,基本就能找到具体原因。

以上就是一文系统梳理MySQL数据库索引失效的七个场景的详细内容,更多关于MySQL索引失效常见场景的资料请关注脚本之家其它相关文章!

相关文章

  • 如何清除mysql注册表

    如何清除mysql注册表

    在本篇文章里小编给大家整理的是关于如何清除mysql注册表的相关知识点内容,有需要的朋友们可以参考下。
    2020-08-08
  • MySQL 游标的作用与使用相关

    MySQL 游标的作用与使用相关

    这篇文章主要介绍了MySQL游标的相关资料,帮助大家更好的理解和使用MySQL数据库,感兴趣的朋友可以了解下
    2021-01-01
  • MySQL的中文UTF8乱码问题

    MySQL的中文UTF8乱码问题

    MySQL从4.x版本开始支持Unicode,3.x只有latin1编码。刚工作的时候就开始用MySQL了,用的php存取,网页xxx.php是gb2312的编码,存进去的数据用php取出来是中文,用phpMyAdmin执行select、update、dump都是中文,没有乱码问题。
    2010-05-05
  • MySql数据库基础知识点总结

    MySql数据库基础知识点总结

    这篇文章主要介绍了MySql数据库基础知识点,总结整理了mysql数据库基本创建、查看、选择、删除以及数据类型相关操作技巧,需要的朋友可以参考下
    2020-06-06
  • MySql使用create index创建索引方式

    MySql使用create index创建索引方式

    本文详细介绍了在MySQL中创建不同类型的索引的方法,包括普通索引、唯一索引、全文索引、空间索引以及复合索引的创建语法
    2026-06-06
  • SQL实现LeetCode(178.分数排行)

    SQL实现LeetCode(178.分数排行)

    这篇文章主要介绍了SQL实现LeetCode(178.分数排行),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • CentOS Linux更改MySQL数据库目录位置具体操作

    CentOS Linux更改MySQL数据库目录位置具体操作

    由于MySQL的数据库太大,默认安装的/var盘已经再也无法容纳新增加的数据,没有办法,只能想办法转移数据的目录,本文整理了一些MySQL从/var/lib/mysql目录下面转移到/home/mysql_data/mysql目录的具体操作,感兴趣的你可不要走开啊
    2013-01-01
  • MySQL 5.0.16乱码问题的解决方法

    MySQL 5.0.16乱码问题的解决方法

    这篇文章主要介绍了MySQL 5.0.16乱码问题的解决方法,需要的朋友可以参考下
    2015-10-10
  • MySQL开启配置binlog及通过binlog恢复数据步骤详析

    MySQL开启配置binlog及通过binlog恢复数据步骤详析

    这篇文章主要给大家介绍了关于MySQL开启配置binlog及通过binlog恢复数据的相关资料,binlog是MySQL最重要的日志,binlog是二进制日志,它记录了所有的DDL和DML语句,除了查询语句select、show等,需要的朋友可以参考下
    2024-06-06
  • 一文学会Mysql数据库备份与恢复

    一文学会Mysql数据库备份与恢复

    数据库备份是在数据丢失的情况下能及时恢复重要数据,防止数据丢失的一种重要手段,下面这篇文章主要给大家介绍了关于Mysql数据库备份与恢复的相关资料,需要的朋友可以参考下
    2022-05-05

最新评论