当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 查询为什么不走索引:函数列、隐式转换和范围条件怎么改

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

来源:17golang原创 2026-07-09 13:31:46 0浏览 收藏

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

要点速览
  • 索引是否被正常使用,要看 EXPLAIN 里的 key、rows、type 和 Extra,不要凭主观感受直接判断。
  • 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 变成 range、ref 这类更合理的访问方式。
  • Extra 字段里有没有明显的额外排序、临时表或者大量的后置过滤逻辑。
MySQL 使用 EXPLAIN 检查 key、rows、Extra 来验证索引改写效果的检查板
SQL改写完成后要用EXPLAIN检查key、rows、type和Extra四个字段,确认索引访问路径真的得到了优化。

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

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

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

相关问题

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

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

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

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

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

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

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

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

收尾检查

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

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go map 并发读写为什么会 panic:加锁、单协程和 sync.Map 怎么选Go map 并发读写为什么会 panic:加锁、单协程和 sync.Map 怎么选
上一篇
Go map 并发读写为什么会 panic:加锁、单协程和 sync.Map 怎么选
Go 1.22 循环变量升级:闭包、goroutine 和测试回归怎么处理
下一篇
Go 1.22 循环变量升级:闭包、goroutine 和测试回归怎么处理
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    389次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    469次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    476次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    420次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    244次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码