从全表扫描到覆盖索引:我是怎么干掉慢查询的

从全表扫描到覆盖索引:我是怎么干掉慢查询的

生产环境一次慢查询拖垮整条业务线,查了三天最后发现问题竟然出在一个JOIN上------这种事我见过太多了。SQL优化不是玄学,它有方法论、有套路、有可复制的路径。今天我就拿一个真实案例,把从发现问题到解决问题的全过程拆开讲透,看完你就能直接上手用。

一、一次真实的慢查询事故

去年我们团队接手了一个电商后台项目,用户反馈"订单列表页加载特别慢"。我登录数据库一看,果然有问题。

这条查询语句大概长这样:

sql

SELECT o.order_id, o.create_time, u.nickname, p.product_name,

oi.quantity, oi.price

FROM orders o

LEFT JOIN users u ON o.user_id = u.user_id

LEFT JOIN order_items oi ON o.order_id = oi.order_id

LEFT JOIN products p ON oi.product_id = p.product_id

WHERE o.create_time >= '2025-01-01'

AND o.status = 'paid'

ORDER BY o.create_time DESC

LIMIT 20;

这条语句在测试环境没什么问题,但到了生产环境,数据量表膨胀到几百万行之后,执行时间直接飙到了8秒以上。用户等不了,投诉不断。

我用 EXPLAIN 看了一下执行计划,结果触目惊心:

id select_type table type possible_keys key key_len ref rows Extra

1 SIMPLE o ALL idx_create_time NULL NULL NULL 1200000 Using where; Using filesort

1 SIMPLE u eq_ref PRIMARY PRIMARY 4 o.user_id 1 NULL

1 SIMPLE oi ref idx_order_id idx_order_id 4 o.order_id 3 NULL

1 SIMPLE p eq_ref PRIMARY PRIMARY 4 oi.product_id 1 NULL

**问题一目了然:**orders 表走了全表扫描,扫描了120万行数据。type=ALL 意味着没有用到任何索引,Extra 里的 Using filesort 说明连排序都在内存里硬排,没有利用索引的有序性。

二、问题拆解:这条SQL到底错在哪

很多人看到慢查询,第一反应是"加索引"。但加索引之前,得先搞清楚问题的根源。

1、WHERE 条件没命中索引

WHERE o.create_time >= '2025-01-01' AND o.status = 'paid' 这两个条件,create_time 字段有索引,但 status 没有。数据库优化器在选择执行路径时,发现 status 的区分度不高(paid 订单占了总量的60%以上),所以干脆放弃了 idx_create_time,直接全表扫描。

2、复合索引缺失导致优化器做了最差选择

如果 (status, create_time) 上有一个复合索引,优化器大概率会走这个索引。因为 status 是等值条件,create_time 是范围条件,复合索引的最左匹配原则刚好能用上。

3、ORDER BY 无法利用索引

虽然 create_time 上有单列索引,但因为查询走了全表扫描,ORDER BY o.create_time DESC 根本用不上这个索引的有序性,导致额外的 filesort 操作。

4、SELECT 列有不必要的字段

o.order_id, o.create_time, u.nickname, p.product_name, oi.quantity, oi.price 这些字段全部需要,这个倒没什么问题。但如果有大字段(比如 TEXT 类型),就会造成回表开销。

三、优化方案:三步走,从8秒到0.3秒

找到了病根,接下来就是对症下药。

**第一步:**加复合索引

sql

ALTER TABLE orders ADD INDEX idx_status_create_time (status, create_time);

加完之后再看 EXPLAIN:

id select_type table type possible_keys key key_len ref rows Extra

1 SIMPLE o range idx_status_create_time idx_status_create_time 13 NULL 48000 Using index condition; Using filesort

扫描行数从120万降到了4.8万,已经是质的飞跃。但还有 Using filesort,说明排序还是有问题。

**第二步:**调整索引顺序,让排序也能用上索引

把复合索引改成 (status, create_time DESC):

sql

ALTER TABLE orders DROP INDEX idx_status_create_time;

ALTER TABLE orders ADD INDEX idx_status_create_time_desc (status, create_time DESC);

再次 EXPLAIN:

id select_type table type possible_keys key key_len ref rows Extra

1 SIMPLE o range idx_status_create_time_desc idx_status_create_time_desc 13 NULL 48000 Using index condition

Using filesort 消失了!因为索引本身就是按 create_time DESC 排序的,数据库可以直接按索引顺序取数据,不需要额外排序。

**第三步:**用覆盖索引减少回表

观察一下 SELECT 的字段,order_id、create_time、status 都在索引里,但 user_id 不在。而 JOIN users 需要 user_id。如果把 user_id 也加进索引:

sql

ALTER TABLE orders DROP INDEX idx_status_create_time_desc;

ALTER TABLE orders ADD INDEX idx_status_time_user (status, create_time, user_id);

优化后的执行计划:

id select_type table type possible_keys key key_len ref rows Extra

1 SIMPLE o range idx_status_time_user idx_status_time_user 17 NULL 48000 Using index condition

执行时间从8秒降到了0.3秒。从全表扫描120万行,到索引范围扫描4.8万行,再到利用覆盖索引避免回表,每一步都有明确的收益。

四、几个容易踩的坑

优化SQL这件事,不是加个索引就完了。有几个坑我必须提一下:

1、索引不是越多越好

每加一个索引,INSERT、UPDATE、DELETE 都要维护这个索引,写操作的开销会增加。我见过一个项目,一张表加了十几个索引,结果查询快了,但写入慢了三倍。索引要加在刀刃上,优先考虑 WHERE、JOIN、ORDER BY 涉及的字段。

2、区分度低的字段别单独建索引

比如 status 字段,如果只有 paid、unpaid、refund 三个值,区分度极低。单独给 status 建索引,优化器大概率不会用。这种字段适合放在复合索引的最左边,配合高区分度字段一起用。

3、LIKE '%xxx%' 几乎无法用索引

左模糊查询会导致索引失效,这是硬伤。如果业务上必须支持模糊搜索,建议走 Elasticsearch,别硬扛。

4、函数操作会让索引失效

sql

-- 索引失效

WHERE YEAR(create_time) = 2025

-- 走索引

WHERE create_time >= '2025-01-01' AND create_time < '2026-01-01'

把函数从字段上移到等号右边,这个小改动能让索引从失效变成生效。

5、EXPLAIN 不是看一次就够的

很多人优化完看一眼 EXPLAIN 就觉得完事了。但数据量变化之后,执行计划可能完全不同。我的习惯是在上线前、上线后、数据量翻倍后各看一次 EXPLAIN,确保优化效果在不同数据规模下都稳定。

五、总结:SQL优化的核心思维

回过头来看,这次优化的本质是什么?不是某个神奇的技巧,而是三个基本动作:

1、让 WHERE 条件能命中索引(复合索引 + 最左匹配)

2、让 ORDER BY 能利用索引有序性(索引列顺序与排序方向一致)

3、让 SELECT 尽量走覆盖索引(减少回表)

这三条做到了,大部分慢查询都能解决。剩下的那些极端情况,再考虑 EXPLAIN 分析、执行计划劫持、SQL重写等进阶手段。

SQL优化这件事,说难也难,说简单也简单。难的是没有标准答案,每条SQL的数据分布、业务场景都不一样。简单的是,方法论就那些,EXPLAIN 一跑,问题基本就藏不住了。

别迷信"银弹",也别一上来就加索引。先看执行计划,再动手改,改完再验证。这个习惯养成了,你会发现大部分性能问题都能在半小时内定位并解决。

💡注意:本文所介绍的软件及功能均基于公开信息整理,仅供用户参考。在使用任何软件时,请务必遵守相关法律法规及软件使用协议。同时,本文不涉及任何商业推广或引流行为,仅为用户提供一个了解和使用该工具的渠道。

你在生活中时遇到了哪些问题?你是如何解决的?欢迎在评论区分享你的经验和心得!

希望这篇文章能够满足您的需求,如果您有任何修改意见或需要进一步的帮助,请随时告诉我!

感谢各位支持,可以关注我的个人主页,找到你所需要的宝贝。

博文入口:山峰哥-CSDN博客 复制到【浏览器】打开即可,宝贝入口:常用软件 宝贝:精品文件

作者郑重声明,本文内容为本人原创文章,纯净无利益纠葛,如有不妥之处,请及时联系修改或删除。诚邀各位读者秉持理性态度交流,共筑和谐讨论氛围~

相关推荐
IT策士1 小时前
Redis 从入门到精通:分布式锁 —— 从 SETNX 到 Redlock
数据库·redis·分布式
云计算磊哥@1 小时前
运维开发宝典027-MySQL03数据库的增删改查
运维·数据库·运维开发
zyplayer-doc1 小时前
继飞书之后,又一款知识库推出了开源 Agent-Native CLI 工具
人工智能·pdf·编辑器·飞书·开源软件
李白的天不白1 小时前
数据库的CEUD
数据库·sql·oracle
linux修理工2 小时前
kafka积压
数据库·分布式·kafka
i220818 Faiz Ul2 小时前
药店管理|基于springboot + vue药店管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·美食分享系统
不吃土豆的马铃薯2 小时前
C++ 正则表达式入门详解
linux·服务器·网络·数据库·c++·正则表达式
sulikey2 小时前
数据库系统概论 - 定义与查询 期末速成课笔记
数据库·笔记·期末考试·数据查询·期末速成·数据库系统概论·数据定义
伶俜662 小时前
鸿蒙原生应用实战(一):从零开发一个短视频编辑器 App
编辑器·音视频·harmonyos
nan madol2 小时前
PolarDB 分布式版(PolarDB-X)
数据库