场景实战:慢查询排查与优化
❓ 面试官:公司线上系统突然报警,数据库 CPU 飙升到 100%,某个接口响应极慢,你该如何排查?
频率:🔥🔥🔥🔥🔥
💡 一句话总结(先抛结论): 我会立即登录数据库执行 show processlist 抓出现场正在执行的耗时 SQL,或者看慢查询日志。如果确认是某条 SQL 的问题,我会用 EXPLAIN 分析它是否漏建了索引、隐式转换或深分页;如果不是 SQL 本身的问题,我会看是否发生了大事务导致的死锁或长事务阻塞。
📝 详细排查步骤:
第一步:紧急止损(保命要紧)
- 执行
show processlist;,如果发现大量状态为Sending data或Copying to tmp table的相同慢 SQL。 - 为了防止把数据库彻底拖垮,我会立刻和业务方确认,如果有必要,马上使用
KILL [id]杀掉这些查询进程。 - 如果请求还源源不断打过来,我会立刻在代码层进行接口降级或限流。
第二步:精准定位问题 SQL
- 通过慢查询日志:找到慢查询日志文件,用
mysqldumpslow聚合出出现次数最多、耗时最长的 SQL。 - 提取出具体的 SQL 语句和参数。
第三步:使用 EXPLAIN 分析(找病因) 把抓到的 SQL 拿到控制台跑一下 EXPLAIN,重点看三个地方:
type是不是ALL(全表扫描)?key是不是NULL(索引失效/没建索引)?Extra里有没有Using filesort(严重损耗 CPU 的文件排序)?
第四步:对症下药(出方案)
- 没索引:直接在线上使用
ALGORITHM=INPLACE(不锁表)的方式补建索引。 - 索引失效:检查业务代码是否做了隐式类型转换(比如 varchar 传了数字),或者使用了左模糊
LIKE '%xx'。修改代码再发布。 - 深分页:比如
LIMIT 100000, 20,跟产品沟通改为游标滚动(传递上一页最大 ID)或者强制不许跳那么多页。 - 死锁或锁等待:如果
EXPLAIN看 SQL 没问题,那可能是被其他事务锁住了。去查information_schema.innodb_locks看看是谁持有了锁一直不放。
🌟 面试加分项(实战经验): 在真实的线上排查中,有一种非常坑的情况叫"索引选择错误"。就是你明明建了很好的联合索引,但 MySQL 优化器"抽风"了,它觉得全表扫描或者走另一个单列索引成本更低,结果选错了索引导致慢查询。 当时我们的临时解决办法是修改代码,在 SQL 里加上 FORCE INDEX (你的索引名) 强制它走正确的索引,快速恢复了线上业务。事后复盘发现是表的数据分布发生了剧烈变化导致统计信息(Cardinality)不准,最后通过执行 ANALYZE TABLE 重新收集统计信息彻底解决了这个问题。
❓ 面试官:如果有一条 SQL:SELECT * FROM order WHERE status = 1 ORDER BY create_time DESC LIMIT 10 非常慢,你怎么优化?
频率:🔥🔥🔥🔥
💡 一句话总结(先抛结论): 这条 SQL 慢通常是因为排序和回表引起的。我会优先为它建立联合索引 idx_status_create_time (status, create_time)。
📝 详细原理解析: 如果这张表没有索引,或者只有单列索引,会出现什么情况?
- 只建了
status索引:引擎会先通过status = 1查出一大批主键,然后全部回表查出完整数据,最后在内存里根据create_time进行排序(Using filesort)。如果status=1的数据有几十万条,这个内存排序和回表的开销是致命的。 - 只建了
create_time索引:引擎会顺着时间的索引从大到小扫,每扫一条就回表查出完整数据,判断status是否为 1。如果最近生成的订单全都是status=2的,它可能要扫描好几万条记录才能凑齐 10 条status=1的数据返回。
优化方案:联合索引
ALTER TABLE order ADD INDEX idx_status_create_time (status, create_time);为什么这个联合索引完美? 根据 B+ 树的排序规则,这个联合索引首先按 status 排序,在 status 相同的情况下,按 create_time 排序。 所以当引擎定位到 status = 1 的数据节点时,它本身就已经按 create_time 排好序了!引擎只需要顺着这棵树往前读 10 条数据,回表 10 次,直接返回,连排序都不用做(彻底消除了 Using filesort)。
🌟 面试加分项(实战经验): 如果 status 的区分度极低(比如订单状态只有"未支付"和"已支付",数据各占 50%),MySQL 优化器有时候会认为:"既然你要查一半的数据,那我干脆不走索引全表扫描算了",这也会导致慢查询。遇到这种情况,如果必须要这么查,我们可以考虑把这部分热点数据同步到 Redis(使用 ZSET 按时间排序),或者使用 Elasticsearch 来做复杂查询,把关系型数据库解放出来。