3545 字
18 分钟
100 道常见 MySQL 数据库题目SQL优化篇
2026-04-16 15:04:52

24. 慢 SQL 如何定位?#

答:

慢 SQL 是数据库性能问题中最常见的一类问题。 定位慢 SQL 的目标是找出:

  • 哪条 SQL 慢
  • 为什么慢
  • 慢在哪一步

通常可以从日志、监控、执行现场三个方向入手。


1)通过慢查询日志定位#

最经典的方式就是开启 慢查询日志(slow query log)

MySQL 可以把执行时间超过阈值的 SQL 记录下来。 比如设置:

long_query_time = 1

表示执行超过 1 秒的 SQL 会被记录到慢查询日志中。

优点

  • 最直接
  • 能抓到真实慢 SQL
  • 适合离线分析

常用分析工具

  • mysqldumpslow
  • pt-query-digest
  • 云厂商提供的慢 SQL 分析平台

这是最常用的定位手段之一。


2)通过 show processlist 查看现场#

如果数据库当前正卡住,可以执行:

SHOW PROCESSLIST;

或:

SHOW FULL PROCESSLIST;

它可以看到当前正在执行的线程,包括:

  • SQL 内容
  • 执行状态
  • 执行时间
  • 是否在锁等待

适合排查:

  • 当前卡住的 SQL
  • 是否有长事务
  • 是否有锁等待
  • 是否有大量连接堆积

3)通过监控系统定位#

生产环境一般都会接入监控系统,比如:

  • Prometheus + Grafana
  • APM 平台
  • 云数据库监控平台
  • 自研 SQL 监控平台

这些系统通常能看到:

  • QPS
  • TPS
  • 平均响应时间
  • Top N 慢 SQL
  • 锁等待
  • CPU / IO / 内存使用情况

优点是:

  • 可视化
  • 可以按时间维度分析
  • 能看到异常波峰波谷

4)从应用日志中定位#

很多系统会在应用层打印 SQL 执行耗时,例如:

  • ORM 慢 SQL 日志
  • JDBC 拦截器
  • 数据访问层统一埋点

这样可以定位:

  • 某个接口触发了什么 SQL
  • SQL 是偶发慢还是持续慢
  • 慢 SQL 和业务行为的对应关系

尤其适合和接口性能一起联动排查。


5)通过 EXPLAIN 分析执行计划#

定位到具体 SQL 后,下一步一般就是执行:

EXPLAIN SELECT ...

看它的执行计划,重点判断:

  • 是否走索引
  • 是否全表扫描
  • 扫描行数多不多
  • 是否使用临时表
  • 是否 filesort
  • 是否回表严重

所以 EXPLAIN 是定位慢 SQL 根因最关键的手段之一。


6)通过锁相关信息定位#

有些 SQL 慢,不是因为查得慢,而是因为在等锁

这种情况要结合看:

  • SHOW PROCESSLIST
  • SHOW ENGINE INNODB STATUS
  • Performance Schema
  • 锁等待监控

常见情况:

  • 一个事务长时间不提交
  • 其他 update / delete / select for update 全部阻塞

这时候慢的根因不是索引,而是锁冲突。


7)定位慢 SQL 的常见排查流程#

一个比较常见的实战排查路径是:

第一步:发现问题#

  • 接口超时
  • 数据库 CPU 飙高
  • 监控报警
  • 用户反馈卡顿

第二步:定位 SQL#

  • 慢查询日志
  • processlist
  • 应用日志
  • 监控平台

第三步:分析原因#

  • EXPLAIN
  • 看索引
  • 看数据量
  • 看是否锁等待
  • 看是否临时表 / filesort

第四步:验证优化方案#

  • 改 SQL
  • 加索引
  • 重新 explain
  • 压测验证

8)实际中慢 SQL 的根因常见有哪些?#

常见原因包括:

  1. 没有索引
  2. 索引失效
  3. 回表过多
  4. 深分页
  5. 排序 / 分组没走索引
  6. 联表太多
  7. 锁等待
  8. 数据量过大
  9. 统计信息不准导致执行计划差
  10. 服务器资源不足

所以“定位慢 SQL”不是只找到 SQL,而是还要找到慢的真正原因


9)总结#

慢 SQL 定位常用手段:

  1. 慢查询日志
  2. show processlist
  3. 监控平台
  4. 应用层 SQL 日志
  5. EXPLAIN
  6. 锁等待分析

一句话总结:

先找到哪条 SQL 慢,再通过执行计划、索引、锁和资源使用情况找出它为什么慢。


25. 有哪些方式优化慢 SQL?#

答:

慢 SQL 的优化,本质上是减少数据库不必要的工作量。 可以从以下几个方向去优化:

  1. SQL 本身写法优化
  2. 索引优化
  3. 表结构优化
  4. 联表和分页优化
  5. 排序分组优化
  6. 架构层优化

下面详细展开。


1)避免 select *#

这是最基础也最常见的优化建议。

为什么不要 select *

  • 会查出不需要的列,增加网络传输
  • 可能导致无法使用覆盖索引
  • 增加回表成本
  • 影响 SQL 可读性

例如:

SELECT id, name FROM user WHERE id = 1;

通常优于:

SELECT * FROM user WHERE id = 1;

2)合理建立索引#

索引是优化慢 SQL 最核心的手段之一。

应该重点考虑给以下字段建索引:

  • WHERE 条件字段
  • JOIN ON 字段
  • ORDER BY 字段
  • GROUP BY 字段

但要注意:

  • 不是索引越多越好
  • 低区分度字段不适合建索引
  • 更新频繁字段慎建索引

3)尽量使用覆盖索引#

如果查询所需字段都在索引中,就可以避免回表。

例如:

SELECT name FROM user WHERE city = '深圳';

如果建联合索引:

(city, name)

那么就可能直接从索引取到结果,不需要回表查主键树。

好处#

  • 减少 IO
  • 查询更快
  • 对大表尤其明显

4)避免索引失效#

常见索引失效场景:

  • 对索引列使用函数
  • 对索引列做计算
  • 隐式类型转换
  • like '%xxx'
  • 联合索引不满足最左匹配
  • or 使用不当
  • !=<>not in

例如:

WHERE YEAR(create_time) = 2024

会导致索引失效,应该改成范围查询:

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

5)分页优化#

深分页是很常见的慢 SQL 来源。

例如:

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

慢的原因是:前面 1000000 条需要扫描后丢弃。

优化方法#

方式一:基于上次最大 ID 翻页#

SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;

方式二:延迟关联#

先查主键,再回表:

SELECT o.*
FROM orders o
JOIN (
SELECT id FROM orders ORDER BY id LIMIT 1000000,10
) t ON o.id = t.id;

6)JOIN 优化#

联表查询慢,常见优化方法包括:

(1)小表驱动大表#

因为嵌套循环连接中,驱动表越小越好。

(2)给 join 字段建索引#

例如:

SELECT *
FROM orders o
JOIN user u ON o.user_id = u.id;

o.user_idu.id 都应该有合适索引。

(3)避免 join 太多表#

表太多时:

  • 执行计划复杂
  • 临时表变多
  • 内存压力大

复杂场景可考虑:

  • 冗余字段
  • 业务层拆分查询
  • 数据异构到 ES

7)避免在数据库里做不必要的计算#

例如:

WHERE price * count > 100

这类写法可能影响索引使用。 可以考虑预计算、冗余字段、或在应用层处理部分逻辑。


8)排序优化#

ORDER BY 如果不能利用索引,往往会出现:

  • Using filesort
  • 临时表
  • 大量内存 / 磁盘排序

优化方式

  • where + order by 尽量共用同一个索引
  • 排序字段顺序与联合索引一致
  • 避免大结果集排序后再截取

例如:

SELECT id, name
FROM user
WHERE city = '深圳'
ORDER BY age;

如果建立索引:

(city, age)

效果往往更好。


9)分组优化#

GROUP BY 常见问题:

  • 使用临时表
  • 排序成本高
  • 数据量大时很慢

优化思路:

  • 先过滤再分组
  • 尽量利用索引分组
  • 必要时做预聚合表 / 宽表

10)子查询优化#

某些子查询会生成临时表,影响性能。 可以尝试改成 JOIN

例如:

SELECT * FROM user
WHERE id IN (SELECT user_id FROM orders);

可以改写为:

SELECT DISTINCT u.*
FROM user u
JOIN orders o ON u.id = o.user_id;

但具体哪个更优,还是要看执行计划。


11)使用 UNION ALL 替代 UNION#

如果业务允许重复,尽量用:

UNION ALL

避免去重成本。


12)控制单次查询数据量#

即使 SQL 本身不慢,一次查几十万行也会拖慢接口。

优化建议:

  • 合理分页
  • 流式处理
  • 分批查询

13)表结构优化#

如果 SQL 怎么改都慢,还要考虑是不是表设计本身有问题:

  • 字段太多,行太宽
  • 大字段和热点字段混在一起
  • 冗余不合理
  • 缺少合理主键

可考虑:

  • 垂直拆表
  • 冷热数据分离
  • 大字段拆出扩展表

14)架构层优化#

当单机 MySQL 已经吃紧时,SQL 优化之外还可以考虑:

  • 读写分离
  • 分库分表
  • 缓存
  • 搜索引擎(ES)
  • 异构报表库

15)总结#

慢 SQL 优化常见手段:

  1. 不用 select *
  2. 建合理索引
  3. 使用覆盖索引
  4. 避免索引失效
  5. 优化分页
  6. 优化 join
  7. 优化排序分组
  8. 减少大结果集
  9. 优化表结构
  10. 必要时架构升级

一句话总结:

慢 SQL 优化的核心是:让数据库扫更少的数据、做更少的计算、走更合适的索引。


26. 怎么看执行计划(EXPLAIN),各个字段含义是什么?#

答:

EXPLAIN 是分析 SQL 执行计划最常用、最重要的工具。 它可以帮助我们看出:

  • SQL 有没有走索引
  • 走了哪个索引
  • 扫描了多少行
  • 是否有临时表
  • 是否有 filesort
  • 是否存在全表扫描

这是 SQL 优化时最核心的分析手段之一。


1)怎么用 EXPLAIN#

只需要在 SELECT 前加上 EXPLAIN

EXPLAIN SELECT * FROM user WHERE name = 'Tom';

它不会真正执行 SQL,而是告诉你优化器准备怎么执行。

对于 MySQL 8.0,也可以用更详细的:

EXPLAIN ANALYZE SELECT ...

不过面试中通常先回答传统 EXPLAIN 即可。


2)执行计划中常见字段#

常见字段包括:

  • id
  • select_type
  • table
  • type
  • possible_keys
  • key
  • key_len
  • ref
  • rows
  • Extra

下面逐个解释。


3)id#

表示查询中每个 SELECT 的标识。 如果是简单查询,通常就是 1。 如果有子查询、union,多条 select 会有不同 id。

作用

  • 判断执行顺序
  • 一般 id 越大越先执行
  • id 相同按从上到下执行

4)select_type#

表示查询类型,常见值有:

  • SIMPLE:简单查询,不包含子查询或 union
  • PRIMARY:最外层查询
  • SUBQUERY:子查询
  • DERIVED:派生表(子查询形成临时表)
  • UNION:union 中后面的查询

作用

帮助判断 SQL 结构复杂度。


5)table#

表示这一行执行计划当前访问的是哪张表。

如果有多表 join,这里会分别显示每张表。


6)type#

这是 非常重要 的字段,表示访问类型,也就是 MySQL 如何查找表中的记录。

性能大致从好到差可以理解为:

system > const > eq_ref > ref > range > index > ALL

常见类型解释如下:

system#

表只有一行,几乎是系统表级别,极少见。

const#

通过主键或唯一索引一次命中一条记录,性能很好。

eq_ref#

多表关联时,通过主键或唯一索引做等值匹配。

ref#

使用普通索引做等值匹配,返回多条可能值。

range#

范围扫描,例如:

  • >
  • <
  • between
  • in

index#

扫描整个索引树。 虽然也是全扫描,但比 ALL 好一些,因为扫描的是索引而不是整表数据。

ALL#

全表扫描。 这是最差的一种常见访问类型,通常意味着没有用好索引。


7)possible_keys#

表示这条 SQL 可能会使用到的索引

注意:

  • 只是“可能”
  • 不代表最终真的用了

它可以帮助你判断:

  • 当前有哪些索引可供优化器选择

8)key#

表示实际使用的索引

这个字段比 possible_keys 更重要。 如果这里是 NULL,通常说明没用索引。


9)key_len#

表示 MySQL 实际使用的索引长度。 它可以帮助分析:

  • 联合索引用到了几列
  • 是否充分利用索引
  • 是否有前缀索引截断

例如联合索引 (a,b,c),如果只用了 a,b,那么 key_len 不会包含 c 的长度。


10)ref#

表示索引列是和谁做比较的。 常见值有:

  • const:和常量比较
  • 某字段名:和另一个表字段比较
  • NULL

例如 join 时可能看到:

ref: test.user.id

表示当前表的索引列和 test.user.id 做匹配。


11)rows#

表示优化器估算的扫描行数。 这个值非常关键。

一般原则:

  • rows 越少越好

如果一个查询只返回 10 条结果,但 rows 估算是 100 万,通常说明效率有问题。

不过要注意:

  • 这是估算值,不一定完全准确
  • 和统计信息有关

12)Extra#

Extra 是非常重要的补充信息,常见值包括:


Using index#

表示使用了覆盖索引。 即查询所需字段全部在索引中,不需要回表。

这是一个好信号。


Using where#

表示 MySQL 在存储引擎返回数据后,还需要在 Server 层继续做条件过滤。

不一定坏,但说明不是所有条件都在索引层完成。


Using temporary#

表示使用了临时表。 常见于:

  • group by
  • distinct
  • 某些子查询 / 排序

一般说明 SQL 还有优化空间。


Using filesort#

表示 MySQL 不能利用索引排序,需要额外做文件排序。 注意“filesort”不一定真的写文件,但表示进行了额外排序操作。

通常说明:

  • order by 没走索引
  • 排序效率不高

Using index condition#

表示使用了索引条件下推(ICP)。

通常是好信号,说明部分 where 条件被下推到存储引擎层过滤。


13)分析执行计划时重点看什么?#

面试里建议重点说这几个:

  1. type

    • 是否全表扫描
    • 是否是 range/ref/const
  2. key

    • 实际使用哪个索引
  3. rows

    • 扫描行数大不大
  4. Extra

    • 有没有 Using temporary
    • 有没有 Using filesort
    • 有没有 Using index

14)一个简单示例#

EXPLAIN SELECT name FROM user WHERE city = '深圳';

如果有联合索引 (city, name),理想情况可能看到:

  • type = ref
  • key = idx_city_name
  • rows 较小
  • Extra = Using index

说明:

  • 走了索引
  • 扫描行数不多
  • 还是覆盖索引
  • 查询性能比较好

15)总结#

EXPLAIN 是 SQL 优化最重要的工具。 重点字段要会看:

  • type:访问方式
  • key:实际索引
  • rows:扫描行数
  • Extra:临时表、排序、覆盖索引等附加信息

一句话总结:

看执行计划的核心,就是判断 SQL 有没有走对索引、扫了多少数据、有没有额外排序和临时表。

Comment seems to stuck. Try to refresh?✨