在后端开发和面试中,SQL 优化几乎是绕不开的话题。
尤其是 MySQL 相关问题,经常会围绕以下几个点展开:
-
如何定位慢查询
-
SQL 语句执行得很慢,如何分析
-
索引概念以及索引底层数据结构
-
聚簇索引和非聚簇索引、回表查询
-
覆盖索引,超大分页优化
-
索引创建的原则
-
什么情况下索引会失效
-
谈一谈你对 SQL 优化的经验
这篇文章就结合实际项目场景,把这些高频问题系统整理一遍,既适合面试复习,也适合平时查漏补缺。
1. 如何定位慢查询
在项目中,如果某个接口响应特别慢,第一步并不是马上去改 SQL,而是先定位到底是哪一段慢。
我之前遇到过一个场景:
接口压测的时候响应时间很高,差不多到了 5 秒左右。这时候我们先从系统层面去看,而不是凭感觉盲猜。
常见定位方式
1)借助链路追踪 / 监控工具定位
如果系统中接入了像 SkyWalking 这样的监控工具,就可以很直观地看到:
-
是哪个接口慢
-
接口内部哪一段耗时高
-
是否是某条 SQL 执行时间过长导致的
也就是说,先通过链路追踪把问题范围缩小,最后定位到具体 SQL。
2)开启 MySQL 慢查询日志
如果项目里没有像 SkyWalking 这样的监控系统,那么也可以直接使用 MySQL 自带的 慢查询日志。
MySQL 提供了慢日志功能,可以设置一个阈值,比如:
-
超过 2 秒 的 SQL
-
自动记录到日志文件中
这样就能通过日志把执行慢的 SQL 找出来。
总结
定位慢查询,核心思路就是两步:
-
先定位是哪个接口、哪个模块慢
-
再定位到具体哪条 SQL 慢
一句话概括就是:
先用监控工具缩小范围,再用慢查询日志锁定 SQL。
2. SQL 语句执行得很慢,如何分析?
当我们已经找到了"慢 SQL",下一步就是分析它为什么慢。
这里最常用的工具就是 MySQL 自带的 EXPLAIN。
EXPLAIN 的作用
EXPLAIN 可以查看一条 SQL 的执行计划,帮助我们判断:
-
有没有命中索引
-
是索引扫描还是全表扫描
-
有没有回表
-
有没有额外的排序、临时表等开销
重点关注哪些字段
1)key 和 key_len
这两个字段主要看是否命中了索引。
-
key:实际使用了哪个索引 -
key_len:使用索引的长度
通过这两个字段,可以判断:
-
索引有没有生效
-
联合索引有没有充分利用
-
是否只命中了部分索引列
2)type
type 非常关键,它反映了 SQL 的访问方式。
常见从好到差大致可以理解为:
-
const -
ref -
range -
index -
all
其中:
-
index说明是全索引扫描 -
all说明是全表扫描
如果看到 all,一般就要警惕这条 SQL 是否还存在优化空间。
3)extra
extra 用来补充说明执行过程中的一些额外信息。
比如常见的:
-
Using where -
Using index -
Using filesort -
Using temporary
如果这里出现了一些不理想的信息,比如额外排序、临时表、回表迹象,就说明这条 SQL 还有优化空间。
分析思路总结
当一条 SQL 很慢时,通常这样分析:
-
先用
EXPLAIN看执行计划 -
看
key / key_len是否命中索引 -
看
type是否出现全表扫描或全索引扫描 -
看
extra是否有回表、排序、临时表等问题 -
再根据结果调整索引或重写 SQL
3. 索引概念以及索引底层数据结构
什么是索引?
索引(Index)本质上是帮助 MySQL 高效获取数据的一种数据结构。
它的核心作用主要有三个:
-
提高数据检索效率,减少全表扫描
-
降低数据库的 IO 成本
-
利用索引的有序性,降低排序成本和 CPU 消耗
你可以把索引理解成一本书的目录。
如果没有目录,你只能一页一页翻;
有了目录,就可以快速定位到你想找的内容。
MySQL 索引底层结构
在 InnoDB 存储引擎中,索引底层采用的是 B+ 树。
为什么是 B+ 树,而不是别的结构?
主要原因有几个:
1)层级更少,查询路径更稳定
B+ 树一个节点可以存储很多键值,所以树的高度通常比较低。
高度越低,磁盘 IO 次数就越少,查询效率也更高。
2)非叶子节点只存索引,叶子节点存数据
这样每个节点能容纳更多索引项,更适合磁盘场景。
3)天然适合范围查询
B+ 树的叶子节点之间通常通过双向链表连接,所以:
-
范围查询效率高
-
排序扫描效率高
这也是为什么数据库索引底层非常适合用 B+ 树,而不是简单的数组、链表或者普通二叉树。
4. 聚簇索引和非聚簇索引、回表查询
什么是聚簇索引?
聚簇索引,也叫聚集索引。
它的特点是:
-
数据和索引放在一起
-
B+ 树的叶子节点保存的是整行数据
-
一张表通常只有一个聚簇索引
在 InnoDB 中,主键索引一般就是聚簇索引。
什么是非聚簇索引?
非聚簇索引,也叫二级索引或辅助索引。
它的特点是:
-
索引和数据分开存储
-
B+ 树叶子节点保存的不是整行数据
-
保存的是对应记录的主键值
-
一张表可以有多个非聚簇索引
什么是回表查询?
当我们通过二级索引 查到主键值后,如果还需要查询完整记录,就需要再到聚簇索引中查一次整行数据。
这个过程就叫做 回表。
回表过程可以这样理解:
-
先走二级索引
-
找到对应的主键值
-
再根据主键值去聚簇索引中查整行数据
这相当于查了两次,所以性能通常不如直接命中聚簇索引,或者使用覆盖索引。
5. 覆盖索引,超大分页优化
什么是覆盖索引?
覆盖索引指的是:
查询使用了索引,并且要返回的列,在这个索引中就已经全部能找到。
这样就不需要再回表了。
举个简单例子
如果根据主键 id 查询,并且返回的字段本身就在索引里,那么就可以直接从索引中拿到数据,一次扫描完成,性能会比较高。
覆盖索引的好处
-
避免回表
-
减少磁盘 IO
-
提高查询效率
这也是为什么平时要尽量避免无脑写 select *。
因为你返回的列越多,就越容易导致无法走覆盖索引。
超大分页为什么慢?
比如分页 SQL:
sql
select * from user order by id limit 100000, 10;
当偏移量非常大时,MySQL 会先扫描前面大量数据,再取后面那一小部分,所以效率很低。
超大分页优化思路
常见做法是:
覆盖索引 + 子查询
先通过索引快速查出目标 id,再根据这些 id 去查完整数据。
例如:
sql
select *
from user
where id in (
select id from user order by id limit 100000, 10
);
更常见的写法也可以是:
sql
select u.*
from user u
join (
select id from user order by id limit 100000, 10
) t on u.id = t.id;
为什么这样更快?
因为子查询里只查 id,通常更容易走覆盖索引。
先把目标 id 缩小出来,再回表查完整数据,总体成本会低很多。
6. 索引创建的原则
索引不是越多越好,而是要建在最有价值的位置。
常见原则如下:
1)数据量较大、查询频繁的表适合建索引
如果一张表本身数据量很少,全表扫描成本也不高,这时候加索引收益并不明显。
2)常作为查询条件的字段适合建索引
比如:
-
where -
order by -
group by
这些高频使用的字段,通常是优先考虑建索引的对象。
3)区分度高的字段更适合建索引
例如身份证号这种字段区分度高,索引效果通常比较好。
而像性别这种值很少的字段,索引价值往往不大。
4)字段内容较长时,可以考虑前缀索引
对于字符串很长的字段,直接建完整索引会占用较大空间,
这时可以考虑使用前缀索引。
5)优先考虑联合索引
如果多个条件经常一起查询,那么比起建多个单列索引,通常更推荐建联合索引。
因为联合索引:
-
更符合真实查询场景
-
可以减少索引数量
-
还能提升组合查询性能
6)控制索引数量
索引会提升查询效率,但也会带来代价:
-
占用更多磁盘空间
-
增加写操作成本
-
插入、更新、删除时要维护索引
所以索引不是越多越好,而是要控制数量。
7)尽量使用 NOT NULL
如果字段本身业务上不允许为空,尽量在建表时就加上 NOT NULL。
这样在索引使用和执行判断上通常更清晰,也更利于优化器处理。
7. 什么情况下索引会失效
这一部分也是面试高频题。
1)违反最左前缀法则
对于联合索引,如果没有从最左列开始使用,索引可能无法生效。
例如联合索引:
sql
(a, b, c)
如果你直接查 b 或 c,通常就无法充分利用这个联合索引。
2)范围查询右边的列无法继续使用索引
例如联合索引是:
sql
(a, b, c)
如果条件中 b 用了范围查询,比如:
sql
where a = 1 and b > 10 and c = 5
那么 c 往往就无法继续有效利用索引。
3)在索引列上进行运算或函数操作
例如:
sql
where age + 1 = 30
或者:
sql
where date(create_time) = '2026-03-11'
这类写法会导致索引失效,因为 MySQL 需要先对字段做计算,无法直接利用原索引结构。
4)字符串不加引号,发生隐式类型转换
例如字段是字符串类型,但 SQL 写成:
sql
where phone = 13800138000
而不是:
sql
where phone = '13800138000'
这可能触发隐式类型转换,从而导致索引失效。
5)like 以 % 开头
例如:
sql
where name like '%zhang'
这种写法无法利用普通索引,因为前缀不确定。
而下面这种通常还能利用索引:
sql
where name like 'zhang%'
8. 谈一谈你对 SQL 优化的经验
如果面试官问"你平时是怎么做 SQL 优化的",可以从下面几个层面来回答。
1)表设计优化
在建表阶段就考虑:
-
字段类型是否合理
-
长度是否合适
-
是否允许为空
-
主键设计是否合适
因为很多 SQL 性能问题,本质上是表设计阶段就埋下了隐患。
2)索引优化
重点关注:
-
是否给高频查询字段建了索引
-
是否合理设计联合索引
-
是否避免冗余索引
-
是否减少回表
-
是否尽量走覆盖索引
3)SQL 语句本身优化
例如:
-
避免
select * -
避免在索引列上做函数、计算
-
避免不必要的子查询
-
避免导致索引失效的写法
-
使用
EXPLAIN分析执行计划
4)架构层面的优化
当数据量进一步增大,仅仅优化 SQL 可能还不够,这时还要考虑:
-
主从复制
-
读写分离
-
分库分表
通过架构手段分摊数据库压力。
5)结合监控工具持续优化
线上环境中不能只靠猜,而要结合:
-
SkyWalking
-
慢查询日志
-
SQL 执行计划
-
压测工具
持续发现瓶颈,再有针对性地优化。
总结
SQL 优化不是只会背几个名词,而是要形成一套完整思路:
-
先定位问题
-
再分析执行计划
-
然后判断是不是索引、回表、分页、排序等原因
-
最后从表设计、索引设计、SQL 写法、架构层面综合优化
面试中关于 SQL 的问题,看起来分得很细,但本质上都是围绕这几件事:
-
能不能定位慢 SQL
-
能不能看懂执行计划
-
懂不懂索引和回表
-
知不知道索引失效场景
-
有没有实际优化思路
把这几个部分串起来,SQL 这块基本就比较完整了。