登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 查询为什么不走索引:函数列、隐式转换和范围条件怎么改

来源:17golang原创

时间:2026-07-09 13:31:46 189浏览 收藏

线上慢查询日志里经常能碰到这类场景:orders 表明明给 created_atuser_id 建了索引,SQL 也写了对应的 where 过滤条件,结果 EXPLAIN 一看 key 是空,rows 扫了几十万行数据。MySQL 不走索引时,先别急着新建索引,很多时候问题出在SQL本身的写法:把函数套在索引列上、字符串和数字类型混着比较、范围条件写得不合理,都会让原本能用的索引直接失效。

要点速览
  • 索引是否被正常使用,要看 EXPLAIN 里的 keyrowstypeExtra,不要凭主观感受直接判断。
  • where 条件里对索引列做函数处理,大多没法按原索引的排序逻辑快速定位数据行。
  • 字段类型和传入值类型不匹配时,大概率会触发隐式转换,尤其是字符串类型的列用数字做比较的场景最常见。
  • 联合索引要符合最左前缀规则,等值条件放前面、范围条件放后面,不能只凭字段“都在索引里”就判定一定生效。

先确认问题不是“压根没用到已有索引”

碰到慢查询很多人的第一反应是加新索引,但生产环境里更稳妥的做法是先排查现有索引有没有机会被用上。比如业务里常见的订单表:

CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  status TINYINT NOT NULL,
  created_at DATETIME NOT NULL,
  amount DECIMAL(10,2) NOT NULL,
  KEY idx_user_created (user_id, created_at),
  KEY idx_created (created_at)
);

如果查询响应慢,先执行一遍 EXPLAIN

EXPLAIN
SELECT id, amount
FROM orders
WHERE DATE(created_at) = '2026-07-09';

这里不需要硬背输出里的每一个字段,先抓四个核心信息就行:type 的取值是否异常,key 字段是否为空,rows 预估扫描的行数是不是太大,Extra 部分是否出现临时表、文件排序或者额外过滤逻辑。确认完现象,再回头排查SQL本身的写法。

MySQL 查询不走索引时检查函数列、隐式转换、范围过宽并改写通过的检查清单图
不走索引先别急着加新索引,先按函数列、隐式转换、范围条件过宽几个方向把SQL条件拆开逐一检查。

常见错误写法一:把函数直接套在索引列上

最容易踩坑的慢查询写法,是为了图方便按天统计数据,把 DATE() 直接套在 created_at 字段上:

SELECT id, amount
FROM orders
WHERE DATE(created_at) = '2026-07-09';

这条SQL写起来很省事,但处理逻辑变成了“先把全表每一行的 created_at 计算出日期结果,再拿结果做比较”。索引里存的是原始 created_at 的字段值,MySQL根本没法直接用这个条件去快速定位符合要求的数据范围。

更稳妥的改法,是把函数从索引列上移走,改成前后闭开的范围条件:

SELECT id, amount
FROM orders
WHERE created_at >= '2026-07-09 00:00:00'
  AND created_at 

这个改写能让 idx_created 正常按时间范围定位数据,业务语义也更清晰。日期筛选、月份筛选、按小时筛选都可以用类似思路:不要对索引列做函数处理,把条件换成索引列能直接比较的边界值。

常见错误写法二:字段类型和查询值类型不匹配

隐式转换的问题比函数列隐蔽很多。比如手机号、订单号这类业务字段经常被定义成 VARCHAR 类型,但业务代码传参的时候不小心传了数字类型:

SELECT id, user_id
FROM orders
WHERE order_no = 202607090001;

如果 order_no 是字符串类型字段,这个查询条件就有很大概率出问题。数据库为了完成两边值的比较,会自动触发类型转换,索引的利用效率会大幅下降,甚至直接变成全表扫描。更安全的写法是保证传入值的类型和字段本身的类型完全一致:

SELECT id, user_id
FROM orders
WHERE order_no = '202607090001';

这个问题在使用ORM框架拼接SQL时也很常见。代码里不要把所有查询参数都当成数字塞入,尤其是订单号、手机号、身份证号、外部流水号这类“看起来像数字但本质是字符串标识符”的字段要格外注意。

通用判断逻辑:索引列尽量保持原样,再谈优化

判断一个where条件是不是对索引友好,可以先记住一个简单的准则:索引列尽量保持完整地出现在比较条件的左侧,不要被函数、表达式和类型转换包裹住。下面这个对照表可以当作改SQL时的快速参考。

问题写法风险点推荐改法
DATE(created_at) = '2026-07-09'函数包裹索引列改成开始时间和结束时间的范围条件。
amount + 0 > 100表达式包裹索引列把运算逻辑移到比较值侧,保持 amount > 100
order_no = 202607090001字符串列和数字做比较传入字符串字面量或者绑定字符串类型参数。
LIKE '%abc'前缀不确定的模糊匹配能改成前缀匹配就用 LIKE 'abc%',否则考虑其他检索方案。

联合索引不是字段都出现就一定生效

联合索引的使用逻辑也很容易被误判。假设有联合索引 idx_user_created(user_id, created_at),下面这种写法通常能正常利用索引:

SELECT id, amount
FROM orders
WHERE user_id = 10086
  AND created_at >= '2026-07-09 00:00:00'
  AND created_at 

因为它先用 user_id 做等值过滤,再用 created_at 做范围过滤,和联合索引的字段排序顺序完全匹配。反过来,如果只过滤 created_at,没有对应的 user_id 等值条件,这个联合索引大概率没法被利用,可能需要单独建时间字段的索引,或者调整现有的查询逻辑。

还有一种常见情况是范围条件在联合索引里放得太早。联合索引里一旦碰到范围条件,后面的字段能不能继续被充分利用,要看优化器的具体判断和条件的形态。实际写SQL的时候,不要只看查询涉及的字段是不是都在索引里,一定要用 EXPLAIN 查看真实的访问路径。

用 EXPLAIN 反向验证改写的实际效果

改完SQL之后,不要凭感觉就判定应该走索引了。至少要用 EXPLAIN 对比改写前后的执行计划结果。

EXPLAIN
SELECT id, amount
FROM orders
WHERE user_id = 10086
  AND created_at >= '2026-07-09 00:00:00'
  AND created_at 

重点关注几个核心信号:

  • key 字段是否从空变成了预期的索引名,比如 idx_user_created
  • rows 字段的预估扫描行数是不是有明显下降。
  • type 字段的取值是不是从 ALL 变成 rangeref 这类更合理的访问方式。
  • Extra 字段里有没有明显的额外排序、临时表或者大量的后置过滤逻辑。
MySQL 使用 EXPLAIN 检查 key、rows、Extra 来验证索引改写效果的检查板
SQL改写完成后要用EXPLAIN检查key、rows、type和Extra四个字段,确认索引访问路径真的得到了优化。

兼容注意:不要为了走索引改动业务逻辑

优化SQL的时候要注意一个核心原则:不要为了让索引看起来生效,把原有的业务语义改出问题。比如按天筛选数据的时候,结束时间最好用下一天的开区间,不要直接写成 23:59:59。如果字段本身带微秒精度,后者很容易漏掉部分符合条件的数据。

另外,隐式转换的问题不能只靠在SQL层补引号来解决。最好回到代码里的参数绑定位置,确认字段定义类型、参数传入类型、序列化方式三者是完全一致的。不然这条SQL临时修好了,后续同一段代码生成的其他查询还是会踩到同样的坑。

相关问题

MySQL 不走索引是不是一定要新建索引?

不一定。先排查SQL是不是把索引列包进了函数、表达式或者触发了隐式转换。如果是写法问题,改SQL往往比新建索引的收益更高、风险更小。

EXPLAIN 里 key 字段有值就代表优化完成了吗?

不代表。还要看 rowstypeExtra。有时候虽然用到了索引,但实际扫描的数据范围仍然很大,查询效果未必能达到预期。

LIKE 查询一定不能用索引吗?

不是。LIKE 'abc%' 这种前缀匹配的写法通常还是有机会利用索引,LIKE '%abc' 这种前面字符不确定的模糊匹配就很难靠普通B+Tree索引快速定位数据。

联合索引的字段顺序应该怎么排?

要结合实际业务的查询条件来排序。通用思路是等值过滤字段放前面,范围字段往后放,同时参考字段区分度、排序需求和实际查询频率,最后用 EXPLAIN 验证实际效果。

收尾检查

MySQL 查询不走索引时,先别急着堆叠新索引。更稳妥的排查顺序是:确认现有可用索引,跑 EXPLAIN 看执行计划,逐一检查函数列、隐式转换、范围条件和联合索引顺序的问题,再对比改写前后的 keyrowstype 几个核心指标。如果SQL写法已经完全符合规范,但扫描范围仍然过大,再考虑补索引、拆分查询或者调整数据存储模型。按这个流程处理,慢查询优化才不会变成反复盲目加索引的无效操作。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>