慢查询治理先做索引还是先改SQL,结论是:先定位瓶颈类型,瓶颈在索引命中率低就先加索引,瓶颈在查询逻辑冗余就先改SQL,顺序错了会白忙一场。
为什么纠结先加索引还是先改SQL,本质是没分清楚瓶颈在哪
很多DBA和开发碰到慢查询,第一反应就是“加个索引试试”,但加完发现有时候有效,有时候没效果,甚至有的查询加了索引反而更慢了,这不是索引没用,而是没有搞明白这条SQL慢的真正原因。
慢查询的瓶颈通常分两类:索引失效型和SQL设计型,索引失效型是指表本身有合适的索引可用,但因为SQL写法、隐式转换或者查询条件设计不合理,导致索引没有走成,SQL设计型则是查询本身要处理的数据量过大,比如多表JOIN顺序不对、在SELECT里写了不需要的大字段、或者明明是OLTP场景却写了一个重聚合的查询。
行业共识认为,慢查询治理的正确顺序应该是:先通过EXPLAIN和慢日志定位瓶颈,再决定是先动索引还是先动SQL,没有定位就动手,基本上靠猜。
慢查询优化先加索引还是改SQL,先看EXPLAIN里这两个信号
拿一条真实慢查询举例:某订单表有2000万行数据,查询条件是WHERE order_status = 1 AND created_at > '2026-01-01' ORDER BY created_at DESC LIMIT 20,执行时间1.8秒,EXPLAIN一看,type是ALL,rows扫描了1200万行,Extra里没有Using index,只有Using filesort。
这个信号非常明确:瓶颈在索引,因为查询条件里两个字段都普普通通,但你一个索引都没建,或者建了单列索引但排序列没覆盖到,这时候先改SQL没有意义你改成什么样,没有索引支撑,该全表扫描还是全表扫描。
什么信号表明瓶颈在索引
type列的值为ALL或index,说明在走全表扫描或全索引扫描key列为NULL,说明没有可用索引rows估算值远大于实际返回行数,说明过滤性极差Extra出现Using filesort或Using temporary,说明排序和分组没有利用索引顺序

只要命中其中任意一条,优先加索引,而且不只是复合索引,要按等值在前、排序在后的原则建,上面那个例子,应该建(order_status, created_at)联合索引,因为order_status是等值条件,created_at既要过滤又要排序。
什么信号表明瓶颈在SQL本身
type已经是ref或range,索引走了,但查询还是很慢Extra有Using where且rows扫描量依然大,说明索引选错了,或者索引区分度太低- SQL里有
SELECT或者捞了十几个用不到的大字段 - JOIN了多张表但关联顺序明显不合理,小表没有驱动大表
- 在WHERE条件中对索引列做了函数运算或隐式类型转换
这种情况下,先加索引能优化的空间很小,改SQL才是正路,比如把WHERE DATE(created_at) = '2026-01-01'改成created_at >= '2026-01-01' AND created_at < '2026-01-02',让索引列不再被函数包裹,这种改造不增加任何索引,但执行效率能提好几个量级。
生产环境慢查询治理实战:什么时候先动索引,什么时候先动SQL
拿一个比较典型的电商系统来说事,运营后台有一批报表查询经常超时,开发团队一开始的惯性做法是给所有涉及到的表都加上索引,结果是把一张宽表加到了8个索引,插入速度直接掉了一半,但查询该慢还是慢。
后来用慢日志和EXPLAIN逐个排查,发现真正的瓶颈类型完全不同。
索引缺失导致的全表扫描,先加索引收益最快
商品表的查询:WHERE brand_id = 1024 AND status = 1 ORDER BY sales_volume DESC LIMIT 10,EXPLAIN显示type=ALL,扫描行数接近商品表全量,这就是典型的索引缺失,不是SQL写法问题,建一个(brand_id, status, sales_volume)联合索引,查询从1.2秒掉到30毫秒,这种场景,改SQL完全没用,必须先加索引。
索引存在但没被用上,改SQL比加索引更治本
订单表的统计查询:SELECT COUNT() FROM orders WHERE YEAR(create_time) = 2026,表上已经有create_time的单列索引,但因为用了YEAR()函数包裹,索引直接失效,走了全表扫描,这时候就算你再加一个索引也解决不了问题函数包裹索引列,任何数据库都很难走索引,正确做法是改成范围条件:

SELECT COUNT() FROM orders WHERE create_time >= '2026-01-01' AND create_time < '2026-01-01'
这个改动不需要新增任何索引,只要原索引存在,执行效率立刻从全表扫描变成索引范围扫描。
SQL本身复杂度过高,加索引只是缓和,改SQL才是根治
一个典型的多表聚合查询,把订单主表、订单明细表、用户表、商品表四张表JOIN在一起做GROUP BY和SUM,EXPLAIN显示每张表都走了索引,但最终执行时间还是3秒多,原因在于:明细表关联出来的中间结果集太大,索引只能优化单表访问路径,优化不了表之间组合出来的数据量膨胀。
这种情况加索引没有意义,需要从业务逻辑上拆SQL,先查出主表符合条件的订单号,再IN查询明细表,最后在应用层做聚合,SQL拆分之后,单条查询的复杂度急剧下降,每条都能走索引,整体耗时反而更低。
业内专家指出,生产环境里有相当大的比例慢查询属于既有索引设计不合理、又有SQL写法缺陷的混合型问题,这种情况下,建议按优先级顺序操作:先做能立刻止血的索引调整,等系统稳定后,再逐步改造SQL写法,最后统一清理冗余索引。
慢查询怎么优化才不走弯路:基于瓶颈类型的决策路径
很多时候你问“mysql慢查询优化先加索引还是改SQL”,得到的答案往往是“看情况”,这没有错,但对实际干活的人来说,“看情况”就等于没说,下面给一条可直接对照的决策路径。
第一步:定位瓶颈,用EXPLAIN说话
在慢查询所在的库上执行EXPLAIN,关注四列:type、key、rows、Extra,别猜,别拍脑袋,数据会告诉你答案。
- 如果你看到
type=ALL或key=NULL,索引缺失是主因 - 如果你看到
type=ref或range但rows依然大,索引选择或SQL写法是主因 - 如果你看到
Extra里有Using temporary或Using filesort,说明排序和分组是瓶颈,优先考虑索引覆盖排序字段

第二步:判断瓶颈类型,照方抓药
| 瓶颈信号 | 优先动作 | 原因 |
|---|---|---|
| 全表扫描,无可用索引 | 先加索引 | 没有索引,改SQL也扫全表 |
| 索引列被函数包了 | 先改SQL | 加再多索引都会被绕过 |
| 多表JOIN中间集膨胀 | 先改SQL拆解 | 索引解决不了关联膨胀 |
| 排序/分组慢 | 先加覆盖索引 | 让索引直接提供有序数据 |
| 索引冗余导致写入慢 | 先删索引再改SQL | 查询优化前提是写入不拖垮 |
第三步:落库前用EXPLAIN验证
不管是先加索引还是先改SQL,改完之后都要重新跑一遍EXPLAIN,确认几个关键值变了没有:key是否变成了预期索引,rows是否明显下降,Extra里的Using filesort是否消失,没变化的优化就是无效优化。
日常做慢查询治理,建议把这条路径固化成流程,拿到慢SQL不要急着动手,先看EXPLAIN,对照上面的表格找到对应瓶颈,然后决定先加索引还是先改SQL,这样每一条优化都能落到具体原因上,不再靠运气。
慢查询治理常见问题解答
问:一条SQL同时有全表扫描和复杂JOIN,先加索引还是先改SQL?
答:先加索引把单表访问路径修好,再处理JOIN顺序,因为JOIN的驱动表访问路径如果还在扫全表,改JOIN顺序的效果会被严重削弱,先把每张表的访问路径修到ref级别,再调整关联顺序和拆解逻辑,每一步的效果都能看得见。
问:加索引之后查询变快了,但写入变慢了,怎么权衡?
答:索引不是越多越好,写入慢的根源通常是索引冗余,而不是索引本身,如果一条SQL因为加索引变快了,但对应表每天有大量INSERT和UPDATE,就要权衡读多还是写多,读多写少的场景,多保留几个关键索引问题不大,写多读少的场景,优先改SQL减少对大宽表的查询依赖,而不是无限加索引,生产环境慢查询治理的最终目标是用最少的索引支撑最多的查询路径。