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 PROCESSLISTSHOW ENGINE INNODB STATUS- Performance Schema
- 锁等待监控
常见情况:
- 一个事务长时间不提交
- 其他 update / delete / select for update 全部阻塞
这时候慢的根因不是索引,而是锁冲突。
7)定位慢 SQL 的常见排查流程
一个比较常见的实战排查路径是:
第一步:发现问题
- 接口超时
- 数据库 CPU 飙高
- 监控报警
- 用户反馈卡顿
第二步:定位 SQL
- 慢查询日志
- processlist
- 应用日志
- 监控平台
第三步:分析原因
EXPLAIN- 看索引
- 看数据量
- 看是否锁等待
- 看是否临时表 / filesort
第四步:验证优化方案
- 改 SQL
- 加索引
- 重新 explain
- 压测验证
8)实际中慢 SQL 的根因常见有哪些?
常见原因包括:
- 没有索引
- 索引失效
- 回表过多
- 深分页
- 排序 / 分组没走索引
- 联表太多
- 锁等待
- 数据量过大
- 统计信息不准导致执行计划差
- 服务器资源不足
所以“定位慢 SQL”不是只找到 SQL,而是还要找到慢的真正原因。
9)总结
慢 SQL 定位常用手段:
- 慢查询日志
- show processlist
- 监控平台
- 应用层 SQL 日志
- EXPLAIN
- 锁等待分析
一句话总结:
先找到哪条 SQL 慢,再通过执行计划、索引、锁和资源使用情况找出它为什么慢。
25. 有哪些方式优化慢 SQL?
答:
慢 SQL 的优化,本质上是减少数据库不必要的工作量。 可以从以下几个方向去优化:
- SQL 本身写法优化
- 索引优化
- 表结构优化
- 联表和分页优化
- 排序分组优化
- 架构层优化
下面详细展开。
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 oJOIN ( SELECT id FROM orders ORDER BY id LIMIT 1000000,10) t ON o.id = t.id;6)JOIN 优化
联表查询慢,常见优化方法包括:
(1)小表驱动大表
因为嵌套循环连接中,驱动表越小越好。
(2)给 join 字段建索引
例如:
SELECT *FROM orders oJOIN user u ON o.user_id = u.id;o.user_id 和 u.id 都应该有合适索引。
(3)避免 join 太多表
表太多时:
- 执行计划复杂
- 临时表变多
- 内存压力大
复杂场景可考虑:
- 冗余字段
- 业务层拆分查询
- 数据异构到 ES
7)避免在数据库里做不必要的计算
例如:
WHERE price * count > 100这类写法可能影响索引使用。 可以考虑预计算、冗余字段、或在应用层处理部分逻辑。
8)排序优化
ORDER BY 如果不能利用索引,往往会出现:
Using filesort- 临时表
- 大量内存 / 磁盘排序
优化方式
- 让
where+order by尽量共用同一个索引 - 排序字段顺序与联合索引一致
- 避免大结果集排序后再截取
例如:
SELECT id, nameFROM userWHERE city = '深圳'ORDER BY age;如果建立索引:
(city, age)效果往往更好。
9)分组优化
GROUP BY 常见问题:
- 使用临时表
- 排序成本高
- 数据量大时很慢
优化思路:
- 先过滤再分组
- 尽量利用索引分组
- 必要时做预聚合表 / 宽表
10)子查询优化
某些子查询会生成临时表,影响性能。
可以尝试改成 JOIN。
例如:
SELECT * FROM userWHERE id IN (SELECT user_id FROM orders);可以改写为:
SELECT DISTINCT u.*FROM user uJOIN orders o ON u.id = o.user_id;但具体哪个更优,还是要看执行计划。
11)使用 UNION ALL 替代 UNION
如果业务允许重复,尽量用:
UNION ALL避免去重成本。
12)控制单次查询数据量
即使 SQL 本身不慢,一次查几十万行也会拖慢接口。
优化建议:
- 合理分页
- 流式处理
- 分批查询
13)表结构优化
如果 SQL 怎么改都慢,还要考虑是不是表设计本身有问题:
- 字段太多,行太宽
- 大字段和热点字段混在一起
- 冗余不合理
- 缺少合理主键
可考虑:
- 垂直拆表
- 冷热数据分离
- 大字段拆出扩展表
14)架构层优化
当单机 MySQL 已经吃紧时,SQL 优化之外还可以考虑:
- 读写分离
- 分库分表
- 缓存
- 搜索引擎(ES)
- 异构报表库
15)总结
慢 SQL 优化常见手段:
- 不用
select * - 建合理索引
- 使用覆盖索引
- 避免索引失效
- 优化分页
- 优化 join
- 优化排序分组
- 减少大结果集
- 优化表结构
- 必要时架构升级
一句话总结:
慢 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)执行计划中常见字段
常见字段包括:
idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
下面逐个解释。
3)id
表示查询中每个 SELECT 的标识。
如果是简单查询,通常就是 1。
如果有子查询、union,多条 select 会有不同 id。
作用
- 判断执行顺序
- 一般 id 越大越先执行
- id 相同按从上到下执行
4)select_type
表示查询类型,常见值有:
SIMPLE:简单查询,不包含子查询或 unionPRIMARY:最外层查询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
范围扫描,例如:
><betweenin
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 bydistinct- 某些子查询 / 排序
一般说明 SQL 还有优化空间。
Using filesort
表示 MySQL 不能利用索引排序,需要额外做文件排序。 注意“filesort”不一定真的写文件,但表示进行了额外排序操作。
通常说明:
order by没走索引- 排序效率不高
Using index condition
表示使用了索引条件下推(ICP)。
通常是好信号,说明部分 where 条件被下推到存储引擎层过滤。
13)分析执行计划时重点看什么?
面试里建议重点说这几个:
-
type
- 是否全表扫描
- 是否是 range/ref/const
-
key
- 实际使用哪个索引
-
rows
- 扫描行数大不大
-
Extra
- 有没有
Using temporary - 有没有
Using filesort - 有没有
Using index
- 有没有
14)一个简单示例
EXPLAIN SELECT name FROM user WHERE city = '深圳';如果有联合索引 (city, name),理想情况可能看到:
type = refkey = idx_city_namerows较小Extra = Using index
说明:
- 走了索引
- 扫描行数不多
- 还是覆盖索引
- 查询性能比较好
15)总结
EXPLAIN 是 SQL 优化最重要的工具。
重点字段要会看:
type:访问方式key:实际索引rows:扫描行数Extra:临时表、排序、覆盖索引等附加信息
一句话总结:
看执行计划的核心,就是判断 SQL 有没有走对索引、扫了多少数据、有没有额外排序和临时表。