欢迎光临
我们一直在努力

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

✅ 精简版

顺序:

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

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

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

    注意:show processlist 只能看到正在运行的,已经执行完的要看慢日志。

    重点看四个指标:执行时间/扫描行数/返回行数/排序、临时表或者锁等待

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

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

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

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

  • type(优先级最高) system > const > eq_ref > ref > range > index > ALL
    • ALL:全表扫描,大表绝对红线,必须优化
    • range:范围查询,比如 where id>100,尚可,注意范围不要过大
    • ref:普通索引等值匹配,正常
  • key:实际使用的索引,null 代表没有用到索引
  • rows:预估扫描行数,数字越大越危险,预估要扫描多少行才找到数据
  • 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:

    select * from `order` where orderNo = 'ORD123456';

    执行过程:

  • 在二级索引 idx_orderNo 找到 orderNo='ORD123456',拿到对应的主键 id=10001
  • 拿着 id=10001 去主键索引(聚簇索引)叶子节点,读取整行全部字段 👉 第二步这个动作就是回表
  • 回表本质:两次 B + 树查找,一次二级索引,一次主键索引,会产生额外 IO,影响性能。

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

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

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

    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 本身逻辑很快,但是被锁住,表现为查询卡住

    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 写法问题

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

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


    高频追问(背)

    Q1:Using filesort 一定很差吗?

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

    Q2:select * 有什么坏处?

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

    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 是当前读,会加锁,会被阻塞。

    赞(0)
    未经允许不得转载:171主机测评 » 【高频面试题】SQL 查询慢怎么排查
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址