【高频面试题】SQL 查询慢怎么排查

✅ 精简版

顺序:

  1. 先拿到慢 SQL:从慢查询日志、数据库监控(Prometheus/Grafana、阿里云 RDS 慢 SQL)找到这条 SQL;
  2. explain看执行计划,重点看 type、key、rows、Extra;
  3. 看是否缺索引、索引失效、回表过多、全表扫描、索引覆盖;
  4. 再判断是不是锁等待:看show processlist /performance_schema,有没有长事务、行锁、表锁阻塞;
  5. 然后看服务器资源:CPU、内存、磁盘 IO,是不是数据库资源打满;
  6. 再看业务层面:是否数据量暴涨、分页深度、join 笛卡尔积,统计信息过时;
  7. 最后验证优化效果,压测观察。

完整排查步骤

1. 捕获慢 SQL(第一步,找到是哪条 SQL)

  • 开启 MySQL慢查询日志 slow_query_log ,设置阈值比如 long_query_time=1s,超过 1 秒自动记录。
  • 云数据库(RDS)直接后台看慢 SQL 报表。
  • 也可以通过 show processlist 实时看当前正在执行的长耗时 SQL。

注意:show processlist 只能看到正在运行的,已经执行完的要看慢日志。
重点看四个指标:执行时间/扫描行数/返回行数/排序、临时表或者锁等待

如果扫描了几百万行却返回了十几行,可能是过滤条件没有索引;

如果扫描的数据不多但是扫描时间很长,可能是锁等待。

2. explain 执行计划(最核心,必讲)

执行 explain select xxx,重点看这几个字段:

  1. type (优先级最高) system > const > eq_ref > ref > range > index > ALL
  • ALL:全表扫描,大表绝对红线,必须优化
  • range:范围查询,比如 where id>100,尚可,注意范围不要过大
  • ref:普通索引等值匹配,正常
  1. key :实际使用的索引,null 代表没有用到索引
  2. rows:预估扫描行数,数字越大越危险,预估要扫描多少行才找到数据
  3. Extra(额外信息,坑基本在这里)
  • Using filesort:文件排序,没用到索引排序,需要额外排序,性能差
  • Using temporary:创建临时表,常见于 group by,消耗大
  • Using index:索引覆盖,最佳!不需要回表
  • Using where:存储引擎返回数据后,MySQL 服务层再过滤

主要看标:

1.全表扫描还是走索引?

2.是否回表过多?(比如,避免select *, 设计覆盖索引)

3.排序和分页?(比如深分页,游标分页)

索引失效常见原因(面试官最爱追问)

  • 索引列做函数运算、隐式类型转换(字符串不加引号)
  • like %前缀通配 like '%abc'
  • or 两边有非索引字段
  • 联合索引最左匹配原则不满足
  • MySQL 优化器判断走索引代价更大,主动放弃索引,走全表扫描(比如筛选后数据占表 30% 以上)

什么是回表?

前提:InnoDB 索引分两种:主键索引(聚簇索引) 、二级索引(普通索引)

二级索引里面存的不是完整行数据,只存【索引列 + 主键】。通过二级索引找到主键之后,再拿主键去主键索引里读取完整行数据,这个二次查找的动作就叫回表。

1. InnoDB 聚簇索引原理

InnoDB 主键索引就是聚簇索引:

  • 叶子节点 = 完整一行的所有数据
  • 表数据本身就放在主键索引的叶子节点,数据和主键索引绑定在一起

二级索引(普通索引、唯一索引):

  • 叶子节点只存:索引字段的值 + 主键 ID ,没有其他业务字段

举个例子

order 表:id(主键), orderNo, user_id, amount 建立普通索引:idx_orderNo(orderNo)

执行 SQL:

sql 复制代码
select * from `order` where orderNo = 'ORD123456';

执行过程:

  1. 在二级索引 idx_orderNo 找到 orderNo='ORD123456',拿到对应的主键 id=10001
  2. 拿着 id=10001 去主键索引(聚簇索引)叶子节点,读取整行全部字段 👉 第二步这个动作就是回表

回表本质:两次 B + 树查找,一次二级索引,一次主键索引,会产生额外 IO,影响性能。

2. 什么情况不会回表?------ 索引覆盖(Using index)

查询需要的所有字段,全部都包含在二级索引里面,不需要去主键索引查数据,就不会回表。

把索引改成联合索引:idx_orderNo(orderNo, user_id, amount)

sql 复制代码
select orderNo,user_id,amount from `order` where orderNo='ORD123456';

explain Extra:Using index,直接从二级索引拿到全部需要字段,不需要回表。

3. 什么时候回表代价很高?

  • 查询出来结果集很大,大量行都要回表,大量随机 IO
  • 这也是 MySQL 优化器有时候有索引但是不走索引的原因: 如果筛选出大量数据,回表太多,优化器评估:直接全表顺序扫描比大量随机 IO 回表更快,于是放弃索引。

✅精简版

InnoDB 主键索引是聚簇索引,叶子节点保存完整行数据;

二级索引叶子节点只存索引列 + 主键。

当我们通过二级索引查询,需要的字段不在二级索引里面,就需要拿着主键去主键索引再次查找完整数据,这个过程叫回表。

如果查询的所有字段都在二级索引里,就是索引覆盖,避免回表,性能更好。

高频追问

Q1:主键查询会不会回表?

A:不会。主键直接走聚簇索引,一次 B + 树查找直接拿到完整行,不存在回表。

Q2:联合索引什么时候触发回表?

A:where 条件命中索引,但是select要查询的字段不在联合索引里面,就需要回表。

Q3:回表是随机 IO 还是顺序 IO?

A:随机 IO。二级索引查出来的主键大概率是离散无序的,去主键索引读取数据的时候,磁盘指针到处跳,随机 IO 开销远高于顺序扫描。

Q4:为什么 MyISAM 没有回表概念?

A:MyISAM 是非聚簇索引,主键索引和二级索引结构一样,叶子节点存的都是磁盘文件偏移地址,所有索引查找都是拿偏移量读文件,没有聚簇索引这个概念,所以不存在回表。

3. 排查锁 (锁等待、死锁)& 长事务

SQL 本身逻辑很快,但是被锁住,表现为查询卡住

sql 复制代码
show engine innodb status;
  • 长事务:事务开启很久不提交,占用行锁,阻塞后面的 DML / 查询(RR 隔离级别下 MVCC 读快照,普通 select 不加行锁;update/delete 会被阻塞)

注意:普通快照读不会被行锁阻塞;当前读(select ... for update)会被行锁阻塞。

  • 存在表锁、意向锁,大量事务等待锁,出现锁等待、事务回滚。

4. 服务器资源层面

  • CPU:MySQL CPU 100%,大概率大量排序、全表扫描、复杂计算
  • IO:磁盘 IO 打满,大量随机 IO,回表过多、缺少索引
  • 内存:buffer pool 太小,热点数据放不到内存,大量磁盘读取

5. 业务 SQL 写法问题

  1. 深度分页:limit 100000,10,需要扫描前面十万行;优化:主键 id 分页 where id>xxx limit 10
  2. join 关联没加索引,产生笛卡尔积,行数爆炸
  3. group by /order by 字段无索引,触发 Using filesort/temporary
  4. 一次性查询大量字段,不 select *,减少回表
  5. 统计信息过时,优化器选错索引(analyze table 更新统计信息)

6. 优化之后验证

优化后再次 explain,观察 type、rows;上线前压测,监控 QPS、RT、慢查询数量变化。


高频追问(背)

Q1:Using filesort 一定很差吗?

不一定。小数据量 filesort 影响很小;大表、大结果集出现 filesort 才是严重问题。本质是排序字段没有索引,MySQL 要在内存 / 磁盘做排序。

Q2:select * 有什么坏处?

  1. 读取不需要的字段,增加网络传输;
  2. 无法触发索引覆盖,必须回表,增加 IO;
  3. 表结构变更,业务容易出问题。

规范:只查业务需要的字段。

Q3:联合索引最左匹配原则是什么?

联合索引 idx_a_b_c(a,b,c),查询条件必须从左开始连续匹配 。 where a=? ✔ where a=? and b=? ✔ where b=? ❌ 跳过左边 a,索引失效 范围查询后面的字段不能走索引 :where a=? and b>10 and c=?,c 无法使用索引。

Q4:什么是索引覆盖?

查询需要的所有列,全部都在索引里面,不需要回主键索引查原始数据,Extra 显示 Using index,性能很高。 比如联合索引包含 select 后面所有字段 + where 条件字段。

Q5:为什么有时候有索引,但是 MySQL 不走索引?

MySQL 优化器会估算成本:如果这条 SQL 查询结果占表数据很大比例(比如 > 20%~30%),走索引需要大量回表随机 IO,优化器判断全表顺序扫描更快,主动放弃索引。

Q6:慢查询日志开启对性能影响大吗?

有一点点开销,线上生产环境一般可以开启,阈值合理设置(1s),只记录慢 SQL,不会大量写入。

Q7:MVCC 和锁的关系?普通 select 会不会被行锁阻塞?

InnoDB RR 隔离级别,普通 select 是快照读,读取历史版本,不加行锁,不会被行锁阻塞; select ... for update /select ... lock in share mode 是当前读,会加锁,会被阻塞。

相关推荐
PHP实战开发录1 小时前
MySQL字段加索引为什么没变快
数据库·mysql·php·开发
天衍四九-1 小时前
【无标题】
前端·spring boot·mysql·nginx·docker
Lysander.Jovian2 小时前
mysql数据库基本使用
mysql
红红谈说2 小时前
标签化推送与已读统计怎么做?一次通知链路的工程复盘
数据库·消息推送·数字乡村·集合运算·已读回执·标签体系·写入放大
Shadow(⊙o⊙)3 小时前
MySQL索引
数据库·mysql
fb_123453 小时前
MySQL运维实战:备份恢复+主从复制+读写分离+MHA高可用(超详细手把手教程)
运维·mysql·oracle
倔强的石头_5 小时前
多环境配置治理:开发、测试、生产连接信息如何隔离
数据库
杨云龙UP5 小时前
一次数据库查询缓慢故障复盘:大表数据增长、SQL全表扫描导致系统响应异常
linux·运维·服务器·数据库·sql·mysql
stark张宇5 小时前
从页、区、段到 B+Tree:InnoDB 表空间如何组织数据?
mysql
2501_933670795 小时前
2027 应用统计学秋招选岗指南:统计、SQL、业务指标如何对应岗位
数据库·sql