在MySQL日常运维和开发过程中,我们经常会遇到一种情况:明明在字段上创建了索引,但查询依然很慢,查看耗时发现似乎进行了全表扫描。要确认索引是否真的被使用、为什么没有被使用,最可靠的手段就是分析MySQL的执行计划。执行计划是优化器对一条SQL语句生成的访问路径方案,通过它可以看到表如何被访问、使用了哪个索引、估算扫描多少行等关键信息。

一、使用EXPLAIN获取执行计划
在SQL语句前面加上EXPLAIN关键字,就可以让MySQL返回该语句的执行计划而不真正执行。这是排查索引问题第一步必须掌握的基础操作。EXPLAIN支持SELECT、DELETE、INSERT、REPLACE和UPDATE等语句,但最常用在SELECT查询上。
下面是一个最简单的用法示例,我们在user表上假设已经对email字段建立了普通索引:
EXPLAIN SELECT id, name, email FROM user WHERE email = 'tom@ipipp.com';
执行后MySQL会返回一张表,包含id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra等列。每一列都反映了优化器做决策时的依据。对于索引排查而言,我们最关心的是possible_keys(可能使用的索引)、key(实际使用的索引)、type(访问类型)和Extra(额外信息)。
二、执行计划关键字段解读
1. type字段
type表示MySQL访问表的方式,性能从好到坏大致为:system、const、eq_ref、ref、range、index、ALL。当type为ALL时,意味着全表扫描,此时如果没有特殊说明,基本可以断定索引没有被有效利用。ref和range通常代表使用了索引进行等值或范围查询,是比较理想的状态。
例如,当type显示为ref,key显示为idx_email,说明优化器选择了email上的索引做等值匹配;如果type为ALL且key为NULL,则证明索引完全没生效。理解type能帮助快速判断查询是否属于低效访问。
2. key与possible_keys
possible_keys展示优化器认为可能适用的索引列表,而key是它实际选定的索引。如果possible_keys不为空但key为NULL,说明优化器在估算成本后放弃了这些索引,往往是因为回表代价过高或索引区分度低。
还有一种情况是possible_keys和key都为NULL,这表示连候选索引都没有,可能是查询条件压根没用到索引列,或者索引建错字段。通过对比这两者,可以确认索引是否在候选范围内以及是否被采用。
3. Extra字段
Extra中会给出很多补充说明,例如Using where表示在存储引擎取行后还做了过滤;Using index表示覆盖索引,不需要回表;Using filesort或Using temporary则意味着额外排序或临时表,通常要优化。如果看到Using where且type为ALL,基本就是全表扫描加过滤,索引失效典型特征。
另外,Extra中若出现“Range checked for each record”之类提示,也说明优化器对索引选择不确定。结合Extra与type,我们能更完整还原优化器的执行逻辑。
三、常见索引不生效场景与排查
1. 对索引列使用函数或运算
在WHERE条件中对索引列套函数,会导致B+树索引无法按原值定位。比如对create_time用DATE函数,或对id做加减法,优化器只能放弃索引。
-- 索引失效写法 EXPLAIN SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01'; -- 改写后索引可能生效 EXPLAIN SELECT * FROM orders WHERE create_time >= '2023-01-01' AND create_time < '2023-01-02';
上面第一段SQL对create_time使用了DATE函数,执行计划通常type为ALL。第二段通过范围条件避开函数,让索引范围扫描成为可能。排查时看到函数包裹索引列,就要警惕。
2. 隐式类型转换
当字段是字符串类型但查询值写成数字,或反过来,MySQL会做隐式转换,相当于对列使用了函数,索引失效。例如phone是varchar类型,写phone=13800000000就会转换。
-- phone为varchar,下面写法导致转换,索引失效 EXPLAIN SELECT * FROM user WHERE phone = 13800000000; -- 正确写法,使用字符串字面量 EXPLAIN SELECT * FROM user WHERE phone = '13800000000';
通过EXPLAIN观察key是否为NULL即可识别此类问题。在代码层统一参数类型,是避免隐式转换的根本方法。
3. 前导模糊查询
LIKE以%开头时,最左前缀原则被破坏,B+树无法定位,只能全表扫。只有后模糊如'abc%'才能用索引。
-- 前导百分号,索引失效 EXPLAIN SELECT * FROM article WHERE title LIKE '%mysql%'; -- 后导百分号,可用索引 EXPLAIN SELECT * FROM article WHERE title LIKE 'mysql%';
如果业务必须前后模糊,可考虑全文索引或搜索引擎,而不是强行用LIKE消耗数据库性能。
四、系统性排查步骤总结
当怀疑索引不生效,建议按以下顺序操作:先用EXPLAIN跑一遍SQL,看type是否为ALL以及key是否为NULL;再检查WHERE条件里索引列是否被函数处理、是否类型不匹配、是否前导模糊;接着确认表统计信息是否过期,可执行ANALYZE TABLE更新;最后审视索引本身设计,例如联合索引顺序是否符合最左前缀。
还可以开启optimizer trace,查看优化器为什么选错计划。但绝大多数情况,靠EXPLAIN的type、key、Extra三板斧就能定位问题。养成写完复杂查询就EXPLAIN的习惯,能大幅减少线上慢查询。
五、一个综合排查示例
假设有一张order表,建立了(user_id, status)的联合索引,但下面查询很慢:
EXPLAIN SELECT * FROM order WHERE status = 1 AND create_time > '2023-01-01';
分析发现,条件跳过了user_id直接查status,不符合最左前缀,联合索引无法使用;而create_time上无索引,导致全表扫描。执行计划里possible_keys为空,key为NULL。解决方案是调整联合索引为(status, create_time),或单独给create_time建索引,再重新EXPLAIN验证type变为range、key有值即可。
通过这种“看计划—找失效原因—改写法或索引—再验证”的闭环,就能稳妥解决MySQL索引不生效的问题。