你一条 SELECT 昨天还秒回,今天却卡了 5 秒——第一反应不应该是「加机器」,而是先定位慢在哪。最常见的原因是没走索引或索引失效,其次是深分页和锁等待。核心思路:先用 EXPLAIN 看执行计划,确认 type 是不是 ALL(全表扫描),再按「索引 → SQL 写法 → 分页 → 锁 → 配置」五个方向逐个排查。本文基于 MySQL 8.0 实测,给出可落地的优化动作与一条标准排查顺序。读完你既能自己救火,也懂该从哪里下手。今天这篇文章,编程狮就把这块讲透。

一、为什么查询会突然变慢
慢查询通常不是一个原因,而是「数据量涨了 + 索引没跟上 + 写法踩坑」三者叠加。MySQL 在表小的时候全表扫描也快,一旦数据到百万级,没索引就会线性变慢。更隐蔽的是「原本能用的索引突然用不上了」——往往是因为你在列上套了函数,或关联列类型不一致。
版本提醒:MySQL 8.0 默认存储引擎是 InnoDB,优化重点在「索引」和「执行计划」,老版本 MyISAM 的锁机制完全不同。
⚠️ 注意:不要盲目加索引。索引会降低写入速度、占磁盘,一张表索引过多反而拖慢
INSERT/UPDATE,要加在「高频查询的过滤列」上。
1.1 触发条件
什么情况一定会慢:在 WHERE 里对字段套函数(如 WHERE DATE(created) = ...);用 LIKE '%关键词' 左模糊;两表 JOIN 的关联列类型不一致;OFFSET 翻到几万页;事务里锁了一行又被别的长事务挡住。这些都是实战里最高频的慢查询来源。
1.2 常见误解
有人认为「慢就是该加内存」。不一定。多数慢查询加个复合索引立竿见影,先 EXPLAIN 再谈硬件,否则加内存也白加。索引命中才是第一杠杆。
二、方向一:补索引与修失效
适合谁:执行计划 type=ALL 或 key=NULL。代价:写入略慢、占空间,但换来读性能数量级提升。
-- 先看执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 1;
-- 高频过滤列加复合索引(左前缀匹配)
CREATE INDEX idx_user_status ON orders (user_id, status);
上面这段做的是先看 MySQL 准备怎么查,发现没用索引就补一个「user_id + status」的复合索引。判断索引有没有生效,主要看 EXPLAIN 结果里的 type 和 rows 两列——加索引前后大致是这样:
加索引前: type = ALL key = NULL rows = 982340 ← 全表扫描
加索引后: type = ref key = idx_user_status rows = 3 ← 走索引了
type 从 ALL(全表扫描)变成 ref(索引等值查找)、rows 从几十万掉到个位数,就说明补对了。另外,MySQL 给的是预估值,想看真实耗时可以用 EXPLAIN ANALYZE(8.0.18 起支持),它会把每一步的实际毫秒数和实际返回行数一起打出来:
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123 AND status = 1;
预估行数和实际行数差得很远时,多半是统计信息过期了,执行 ANALYZE TABLE orders; 刷新一下再测。系统入门建议先过一遍 MySQL 教程 的索引章节打底。
补索引不是越多越好。一个常见反模式是给每张表每个字段都建索引,结果写入时 MySQL 要同步维护一堆 B+ 树,INSERT / UPDATE 反而变慢,磁盘占用也上去了。正确做法是只给「高频查询的过滤列和排序列」建索引:比如订单表按 user_id 查得多,就建 (user_id, status) 复合索引;而 status 单独查得少,不必单列建。索引选型也有讲究,等值查询用 B+ 树索引最稳,范围 + 排序场景配合联合索引的左前缀原则,能省下大量回表开销。还有一类坑是隐式类型转换:当关联列一边是 int、一边是 varchar,MySQL 会做类型转换导致索引失效,表现为 EXPLAIN 里明明有索引却用不上。遇到「有索引但没走」,第一反应是查列类型是否一致、是不是在列上套了函数。
三、方向二:改 SQL 写法避坑
适合谁:EXPLAIN 显示有索引但没用上。代价:需改写业务 SQL,工作量小收益大。
常见失效写法:WHERE YEAR(created)=2026(函数包裹)、WHERE phone LIKE '%138'(左模糊)、WHERE a=1 OR b=2 且只有单列索引。改成范围查询、右模糊、或拆成 UNION 即可让索引生效。速查可翻 MySQL 速查手册 的索引优化段落。
四、方向三:深分页改造
适合谁:LIMIT 100000, 20 这类大偏移。代价:需改分页逻辑,但性能提升最明显。
深分页会先扫描前 10 万行再丢弃,纯浪费。改成「游标分页」:用上一页最后一条的 id 当起点。
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;
这样直接定位,跳过扫描,性能从线性退化变成常数级。新版 MySQL 8 的窗口函数也能在分页里派上用场,值得单独了解。
五、方向四与五:锁等待与配置
| 方向 | 现象 | 动作 |
|---|---|---|
| 锁等待 | 查询卡住无返回 | 查 SHOW PROCESSLIST, kill 长事务;缩短事务范围 |
| 配置/内存 | 整体偏慢、磁盘 IO 高 | 调大 innodb_buffer_pool_size;升级 SSD |
图:慢查询排查五步顺序图(additive,待人工补图)
踩坑清单:
- 先 EXPLAIN 再动手:看不到执行计划就优化是盲改。
- 复合索引看左前缀:
(a,b)能加速a和a+b,但单独查b用不上。 - 深分页必改造:
OFFSET越大越慢,早点换游标。 - 长事务是隐形杀手:事务里别做无关慢操作,及时提交释放锁。
- 查连接需要权限:
SHOW PROCESSLIST要看到全部连接得有PROCESS权限,KILL别人的连接则需要CONNECTION_ADMIN(旧版本是SUPER)。没权限时你只能看到自己账号的连接,这时候要找 DBA 协助,别以为"查不到就是没有"。
慢查询优化还有一条常被忽略的路径:慢查询日志。开启 slow_query_log 并设置 long_query_time(比如 1 秒),MySQL 会把超过阈值的 SQL 记下来,你再拿去 EXPLAIN,比凭感觉猜高效得多。生产环境建议周期性 review 这批日志,找出高频慢 SQL 集中治理。另外,缓存层也能显著降压:把热点只读结果放 Redis,或从应用层做结果缓存,能绕开重复的全表扫描。但要注意缓存与数据库的一致性,写后及时失效,避免脏读。最后提醒,优化是权衡:加索引慢写入、加缓存多一份一致性成本,按业务读写比取舍,别为极致性能牺牲可维护性。

总结
MySQL 查询变慢,按「索引 → 写法 → 分页 → 锁 → 配置」顺序排查,九成问题在前两步。核心工具是 EXPLAIN,看到全表扫描就补索引或修写法,别急着加硬件。想深入实战调优,可以看 MySQL 8 教程 补充窗口函数与执行计划细节。
要点带走:
- 慢查询先
EXPLAIN,确认是否全表扫描; - 复合索引遵循左前缀,避免在列上套函数;
- 深分页换游标,长事务及时提交避免锁等待。
下一步想把数据库这块学扎实,建议拿你自己项目里最慢的那条 SQL 实操一轮:先 EXPLAIN 看执行计划,加索引前后各跑一次对比 rows 的变化,再试着改写 SQL 看计划会不会跟着变——动手走一遍,比看十篇教程记得牢。
延伸学习
想把这块知识系统补齐,可以按这个顺序来:
- 先过一遍 MySQL 索引与优化课程,把索引与执行计划铺平;
- 实战参考 MySQL 性能调优实战笔记 看真实案例加深印象;
- 写完后想顺手整理 SQL,SQL 格式化工具 能一键规整语句。
常见问题
Q:EXPLAIN 里的 type=ALL 是什么意思?
A:ALL 表示全表扫描,MySQL 逐行遍历整张表来匹配,数据量大时必然慢。理想是 ref/range/const,出现 ALL 就该检查是否缺索引或索引失效。
Q:加了索引为什么还是慢?
A:可能索引失效了——比如在列上套函数、LIKE 左模糊、或关联列类型不一致。先用 EXPLAIN 确认 key 列是否真的用上了索引,再查 SQL 写法是否踩了上面说的坑。
Q:LIMIT 翻页越往后越慢怎么破?
A:这是深分页典型症状。改用游标分页:用上一页最大 id 做 WHERE id > ? ORDER BY id LIMIT n,跳过前 N 行的扫描,性能从线性退化变成常数级。

免费 AI IDE



