news 2026/5/19 17:06:01

别再写全表扫描了!四步教你搞定 90% 的慢查询

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别再写全表扫描了!四步教你搞定 90% 的慢查询

别再写全表扫描了!四步教你搞定 90% 的慢查询

你有没有遇到过这种场景:线上接口突然变慢,用户疯狂投诉,DBA 让你赶紧查 SQL,你盯着 Explain 结果看了半天,依然不知道问题出在哪里?别慌,这篇文章就是写给你的。我会用真实的业务案例,手把手带你从 Explain 的每一个字段入手,一步步把一条 8 秒的慢查询优化到 50 毫秒以内。不讲虚的,全是干货,看完你就能直接上手改。

一、慢查询的根源:为什么你的 SQL 跑不快

很多开发同学在写 SQL 的时候,习惯"先跑通再说",根本不关心执行计划。等到数据量上来了,问题就爆发了。其实慢查询的原因无非就那么几个,但真到排查的时候,大多数人却抓不住重点。

1、全表扫描是最常见的杀手。当 WHERE 条件里的字段没有索引,MySQL 只能一行一行地扫整张表,数据量一过十万,性能直接崩盘。

2、索引用了但没用对。比如你给一个状态字段建了索引,但这个字段只有 0 和 1 两个值,区分度极低,优化器直接选择全表扫描反而更快。

3、JOIN 顺序不合理。多表关联的时候,MySQL 优化器会自动选择驱动表,但有时候它的选择并不是最优的,尤其是在数据量不均匀的情况下。

4、隐式类型转换。这是最容易被忽略的问题。比如字段是 varchar 类型,你却用数字去比较,索引直接失效。

举个最典型的例子,下面这条 SQL 在我们项目里曾经让接口响应超过 8 秒:

sql

SELECT * FROM orders o

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

WHERE o.create_time > '2024-01-01'

AND o.status = 1

ORDER BY o.id DESC

LIMIT 100;

这条语句看起来很普通,但在 orders 表有 500 万数据的情况下,跑一次要 8 秒多。问题到底出在哪?我们用 Explain 来看。

二、Explain 到底怎么看?别只盯着 type 列

很多人学 Explain 只知道看 type 是不是 ref 或者 range,但实际上 Explain 的每一个字段都有它的含义。下面我把最关键的几个字段拆开来说。

字段名 含义 重点关注什么

id 查询的执行序号 id 越大,执行越靠后;id 相同说明是同一步

select_type 查询类型 看到 DERIVED 或 SUBQUERY 要警惕

table 当前访问的表 关注驱动表是哪张

type 访问类型 从好到差:system > const > eq_ref > ref > range > index > ALL

possible_keys 可能用到的索引 看看有没有漏掉的索引

key 实际使用的索引 和 possible_keys 对比,看是否选对了

key_len 索引使用的字节数 越短越好,说明用到了前缀索引

rows 预估扫描行数 这个数字越小越好,但只是预估

Extra 额外信息 看到 Using filesort、Using temporary 就要注意了

回到刚才那条慢 SQL,我们看一下它的 Explain 结果:

id select_type table type possible_keys key key_len rows Extra

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

1 SIMPLE u eq_ref PRIMARY PRIMARY 8 1 NULL

问题非常明显:orders 表的 type 是 ALL,也就是全表扫描,而且 Extra 里出现了 Using filesort,说明排序也没用上索引。possible_keys 显示 idx_create_time 是可用的,但 key 是 NULL,说明优化器压根没用这个索引。

三、优化实战:四步把 8 秒压到 50 毫秒

知道了问题在哪,接下来就是动手改。我把整个优化过程分成四步,每一步都有明确的目的。

1、先解决排序问题。原 SQL 按 o.id DESC 排序,但 id 本身没有和过滤条件组合成联合索引。我们把 idx_create_time 改成联合索引:

sql

ALTER TABLE orders DROP INDEX idx_create_time;

ALTER TABLE orders ADD INDEX idx_ctime_status (create_time, status, id);

这个索引的设计思路是:create_time 和 status 是等值和范围查询的字段,放在最前面;id 放在最后是为了覆盖 ORDER BY,避免 filesort。

2、再看 JOIN 的驱动表。原 SQL 用 orders 做驱动表,但 orders 有 500 万行,而 users 只有几万行。虽然 users 走了 eq_ref,但 orders 的扫描成本太高。我们用 FORCE INDEX 强制让 orders 走新索引:

sql

SELECT * FROM orders o FORCE INDEX(idx_ctime_status)

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

WHERE o.create_time > '2024-01-01'

AND o.status = 1

ORDER BY o.id DESC

LIMIT 100;

3、优化后再看 Explain:

id select_type table type possible_keys key key_len rows Extra

1 SIMPLE o range idx_ctime_status idx_ctime_status 12 18432 Using index condition

1 SIMPLE u eq_ref PRIMARY PRIMARY 8 1 NULL

可以看到:type 从 ALL 变成了 range,rows 从 512 万降到了 1.8 万,Extra 里的 Using filesort 也消失了。

4、最终结果:查询时间从 8.2 秒降到了 47 毫秒,提升了将近 175 倍。

四、更多容易踩的坑:这些细节决定成败

优化不是改完索引就完了,下面这些细节很多人都会忽略,但在高并发场景下,它们才是真正的瓶颈。

1、LIMIT 偏移量过大的问题。比如 LIMIT 1000000, 10,MySQL 需要扫描 1000010 行然后丢掉前 100 万行。这种场景建议用游标或者记录上次的 ID:

sql

-- 不推荐

SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;

-- 推荐:用上次最后一条的 ID

SELECT * FROM orders WHERE id > 上次最后的ID ORDER BY id LIMIT 10;

2、COUNT(*) 和 COUNT(1) 的区别。在 InnoDB 引擎下,COUNT() 会被优化成扫描最小的二级索引,而 COUNT(1) 和 COUNT(列) 反而可能更慢。所以别纠结了,直接用 COUNT()。

3、OR 条件导致索引失效。下面这条 SQL 看起来索引都能用,但实际上只会走一个:

sql

SELECT * FROM orders WHERE user_id = 100 OR status = 1;

改成 UNION ALL 往往更快:

sql

SELECT * FROM orders WHERE user_id = 100

UNION ALL

SELECT * FROM orders WHERE status = 1 AND user_id != 100;

4、函数操作导致索引失效。这是最经典的坑:

sql

-- 索引失效

SELECT * FROM orders WHERE YEAR(create_time) = 2024;

-- 改成范围查询,索引生效

SELECT * FROM orders WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';

五、建立自己的 SQL 优化 checklist

经过这么多年的实战,我总结了一套自己的排查清单,每次遇到慢 SQL 就按这个顺序过一遍,基本不会漏。

1、先跑 Explain,看 type 是不是 ALL 或者 index,如果是,优先考虑加索引或改索引。

2、看 Extra 有没有 Using filesort 或 Using temporary,有的话优先通过调整索引或改写 SQL 消除。

3、看 key_len,如果很短但 rows 很大,说明索引区分度不够,需要考虑联合索引。

4、检查 WHERE 条件里有没有函数、隐式转换、OR 等导致索引失效的写法。

5、如果单表优化已经到极限了,再考虑是不是需要改业务逻辑,比如分页方式、查询条件等。

☆ 记住一句话:最好的 SQL 优化,不是让数据库跑得更快,而是让查询的数据量变得更少。索引是手段,减少扫描才是目的。

六、写在最后

SQL 优化这件事,说难也难,说简单也简单。难的是你要理解 MySQL 优化器的决策逻辑,简单的是只要你养成看 Explain 的习惯,80% 的慢查询都能在开发阶段就被消灭。

不要等到线上出了问题才去查 Explain,那时候用户已经在骂人了。把优化的思维融入到日常写 SQL 的每一个习惯里,这才是真正的功力。

希望这篇文章能帮你在下次遇到慢 SQL 的时候,不再手足无措。如果你也有自己的优化案例,欢迎在评论区交流,咱们一起进步。

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

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

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

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

博文入口:https://blog.csdn.net/Start_mswin 复制到【浏览器】打开即可,宝贝入口:https://pan.quark.cn/s/b42958e1c3c0 宝贝:https://pan.quark.cn/s/1eb92d021d17

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/19 17:03:06

终极指南:5步掌握MPh,让COMSOL仿真效率提升300%

终极指南&#xff1a;5步掌握MPh&#xff0c;让COMSOL仿真效率提升300% 【免费下载链接】MPh Pythonic scripting interface for Comsol Multiphysics 项目地址: https://gitcode.com/gh_mirrors/mp/MPh MPh&#xff08;Pythonic scripting interface for Comsol Multip…

作者头像 李华
网站建设 2026/5/19 17:01:24

FinalShell的下载安装使用教程

FinalShell的下载安装使用教程 在浏览器输入 FinalShell官网双击打开安装包点击安装即可 如何使用 双击FinalShell双击ai_node2&#xff0c;进入下图模式则成功

作者头像 李华
网站建设 2026/5/19 17:01:14

龙芯2K3000与国产OS在轨道交通AFC系统中的工程实践

1. 项目概述&#xff1a;当国产芯遇上城市动脉每天早晚高峰&#xff0c;地铁站里人头攒动&#xff0c;闸机开合的“嘀嘀”声此起彼伏。你可能没留意&#xff0c;支撑这套庞大自动售检票系统&#xff08;AFC&#xff09;稳定运行的“大脑”&#xff0c;正经历一场静默而深刻的变…

作者头像 李华
网站建设 2026/5/19 17:00:37

origin已停止工作怎么解决呢

已解决&#xff01;&#xff01;&#xff01; 尝试过更改管理员打开权限、删除系统字体等均不行。 后面问的AI&#xff0c;说可能是VC的版本太低&#xff0c;不兼容了&#xff0c;核对了一下版本的确是需要重新下载安装&#xff0c;于是在Microsoft Build 2026网页下载了最新…

作者头像 李华
网站建设 2026/5/19 16:59:53

基于YOLOv5的AI视觉瞄准系统:从理论到实践的深度解析

基于YOLOv5的AI视觉瞄准系统&#xff1a;从理论到实践的深度解析 【免费下载链接】AI-Aimbot Worlds Best AI Aimbot - CS2, Valorant, Fortnite, APEX, every game 项目地址: https://gitcode.com/gh_mirrors/ai/AI-Aimbot 在计算机视觉和游戏技术融合的前沿领域&#…

作者头像 李华