暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SQL 为什么这么慢?80% 的程序员忽略了这一步!

解压泡泡糖 2025-04-26
49

你是否遇到过这样的场景:

一条看似简单的 SQL,查询几十条数据却跑了几十秒;
索引也加了,写法也没问题,就是慢;
数据量一大,系统就卡得不行……

这不是你不会写 SQL,而是你忽略了数据库在背后“怎么执行”它。

真正决定 SQL 快慢的,并不是你写出来的那条语句,而是数据库执行它的方式——也就是 执行计划(Execution Plan)。

今天我们就来聊聊:

  • 执行计划到底是什么?

  • 它为什么会让你的 SQL 慢下来?

  • 如何正确使用 EXPLAIN?

  • 实战中常见的 3 个索引失效场景


什么是执行计划?

执行计划是数据库优化器根据你的 SQL 生成的“执行路径”。

简单说,就是:

“我要这么查、从哪查、怎么查、用不用索引、要不要排序、是否创建临时表”等一系列动作的安排。

在 MySQL 中,你可以通过如下语句看到它


EXPLAIN SELECT * FROM 表名 WHERE 条件;

或者:

EXPLAIN ANALYZE SELECT ...  -- MySQL 8.0+

这一步,就是你和数据库真正“对话”的开始。


一个简单查询的“意外慢查”案例

看起来很正常的 SQL:

SELECT * FROM orders WHERE user_id = 123 AND status = 'PAID';

你还贴心地加了索引:

CREATE INDEX idx_user_status ON orders(user_id, status);

结果执行计划一查:

EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'PAID';

输出内容大概是:

vbnet

type: ALL
rows: 1053247
key: NULL
Extra: Using where

???全表扫描?索引根本没用上!

为什么会这样?


三个让你 SQL 慢下来的执行计划陷阱

1. 索引顺序不当,最左前缀不满足

你加的是 (user_id, status)
 没错,但如果你写的查询语句是这样:

WHERE status = 'PAID'

或者换了顺序,写成:

WHERE status = 'PAID' AND user_id = 123

都会导致 最左前缀原则失效,索引无法命中。

💡 索引组合字段要从左到右依次命中才有效。


2. 数据分布太集中,索引命中不划算

假设 90% 的订单 status 都是 'PAID',优化器可能判断:

与其走索引筛 90%,再回表拿数据,不如直接全表扫一遍快。

这种情况即使你建了索引,MySQL 也可能选择不用。


3. SELECT * 导致回表太重

你查询了所有字段,而索引中只包含部分字段。数据库查到索引命中的行后,还得“回表”到主表查全量字段。

回表是随机 IO,数据一多就慢得飞起。优化器可能直接判断:

回表太费,干脆直接全表扫。

💡 解决方法:

  • 减少字段数量,只查需要的字段

  • 使用 覆盖索引(SELECT 的字段完全包含在索引中)


如何看懂执行计划的重点字段?

用 EXPLAIN
 查询后,会看到这些:

字段
含义
越优越好值
id
查询的执行顺序
越大优先
type
访问类型
const > ref > range > index > ALL
key
实际使用的索引名
非 NULL 表示命中索引
rows
预估扫描行数
越小越好
Extra
是否排序、是否回表等
尽量避免 Using temporary
 和 Using filesort

MySQL 优化器会根据成本估算(Cost-based)来决定是否使用索引,并不是什么“有索引就一定快”。


SQL 性能优化的实用建议

  1. 用 EXPLAIN
     分析每一条慢查询

  2. 不要随意使用 SELECT *

  3. 建立索引时,要考虑字段顺序和选择性

  4. 索引字段不要使用函数(如 DATE(create_time)
     会失效)

  5. 结合业务场景,考虑覆盖索引或冗余字段

  6. 慢查询日志 + 执行计划联合分析,效果更佳

文章转载自解压泡泡糖,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论