🧩 场景题题干(面试官常用表述)
现象:MySQL CPU 飙升,应用接口大量超时。
你如何定位慢 SQL?如何证明是 SQL 问题?如何修复并避免再次发生?
💡 一句话思路:先判断是“DB 扛不住(资源/连接/锁)”还是“SQL 本身慢(执行计划)”,然后用慢日志/EXPLAIN/锁等待证据闭环,最后给出短期止血与长期治理。
📚 学习正文:如何把问题定位到“具体 SQL / 具体索引 / 具体根因”?
交互图:慢 SQL 定位闭环(点步骤看要点与证据)
这张图的目标是把排查过程做成可复述的面试话术:每一步都要能给出“证据”和“下一步动作”,避免只会说“看慢日志、加索引”。
1) 确认慢在 DB(先别急着看 EXPLAIN)
- 应用侧拆分:把接口总耗时拆出 DB 耗时(日志/APM),证明“慢主要在 DB”。
- DB 侧指标:CPU/IO、活跃连接、TPS/QPS、锁等待是否异常。
- 交叉验证:同一条 SQL 直连执行或在只读库执行对比,排除应用线程池/GC 问题。
2) 锁定 Top SQL(找“最贵”的,而不是“最慢的单条”)
- 优先看 TopN:按总耗时 = 平均耗时 × 次数排序,通常比只看“最慢单条”更有效。
- 看正在执行:SHOW FULL PROCESSLIST 快速定位热点语句与大事务。
- 补充维度:SQL 指纹(去参数化)、表/索引命中情况、调用入口(哪个接口/哪个 job)。
3) 读执行计划(把“慢”翻译成可解释的原因)
- 扫描量:type=ALL / rows 很大 → 先缩小扫描范围(过滤条件/索引)。
- 排序/临时表:Extra 出现 filesort/temporary → 优化 order/group 或加合适联合索引。
- 回表:用覆盖索引减少回表(避免 select *)。
- 注意:rows 是估算值;需要结合数据分布、选择性和真实运行耗时验证。
4) 排查锁等待(很多“慢 SQL”其实是“等锁慢”)
- 典型根因:大事务长时间持锁、热点行更新、间隙锁/Next-Key Lock、DDL 元数据锁。
- 证据:performance_schema 的锁表、等待事件、事务信息(需要权限)。
- 止血:缩短事务、拆批、降低隔离级别/减少范围更新、热点行拆分(例如分桶计数)。
5) 落地优化并验证(必须形成“优化前后对比”)
- SQL/索引优化:对齐 where/order/group 的联合索引、覆盖索引、改写分页(seek)。
- 验证方式:压测/回放同一批请求,对比 P95/P99、CPU/IO、rows examined 等指标。
- 上线策略:灰度发布 + 观察告警;必要时准备回滚(索引变更尤其要谨慎)。
6) 长期治理(把“偶发事故”变成“可控流程”)
- 告警:慢 SQL(耗时+次数)双维度告警,跟踪到具体接口与负责人。
- 流程:上线前 SQL 评审 + 数据量级预估 + 必要的压测。
- 容量:连接池/线程池/实例规格与峰值 QPS 匹配,预留突发冗余。
- 数据治理:归档历史数据、冷热分离、控制表膨胀。
1) 先止血:控制影响面,避免 DB 被持续打爆
短期目标:让 RT/错误率下降,让 DB 恢复可用
手段(按优先级):限流/降级 → 熔断慢接口 → 摘掉异常实例 → kill 高危语句(谨慎) → 临时扩容/读写分离
2) 证据链第一步:把慢拆成“应用慢”还是“DB 慢”
- 应用侧:把总耗时拆分(业务计算/DB/RPC/缓存),能明确“慢在 DB”。
- DB 侧:慢日志、当前运行语句、锁等待、CPU/IO 指标能给出“DB 慢”的证据。
- 交叉验证:拿同一条 SQL 直连执行,或在只读库执行对比,排除应用逻辑问题。
3) 快速定位:三件套 + 两个补刀
三件套:慢日志/APM → EXPLAIN(ANALYZE) → 压测/回放复现
两个补刀:锁等待分析 → 连接池/线程池耗尽分析
4) 把“慢”归类(不同根因用不同打法)
- 执行计划问题:全表扫/回表严重/文件排序/临时表。
- 锁等待问题:大事务、热点行更新、间隙锁导致大量等待。
- 资源瓶颈:CPU 打满、IO 打满、buffer pool 不够。
- 连接/线程问题:连接池耗尽、线程上下文切换、连接数爆炸。
5) 定位到具体 SQL:从 DB 里把“最贵的”揪出来
-- 1) 看正在执行(立即止血/定位热点)
SHOW FULL PROCESSLIST;
-- 2) MySQL 8:看锁等待/锁对象(需要 performance_schema)
SELECT * FROM performance_schema.data_locks;
-- 3) 慢日志:找到 TopN(示意,实际用 pt-query-digest 更高效)
-- slow_query_log / long_query_time 配置后再分析
6) 定位到具体原因:学会读 EXPLAIN 的“危险信号”
重点关注:
type=ALL/index(扫描大)
rows 很大(扫描量大)
Extra=Using filesort / Using temporary(排序/临时表)
key 为空或不符合预期(没命中索引)
7) 常用优化策略(能讲出“为什么有效”)
- 对齐索引:让 where/order/group 的过滤与排序走上合适的联合索引(最左前缀)。
- 减少回表:覆盖索引、减少 select *、控制返回列。
- 避免大 offset:seek 分页(基于 lastId/lastTime),或延迟关联。
- 拆分大事务:减少锁持有时间,降低死锁概率。
- 架构性治理:热点读缓存、读写分离、分库分表、异步化。
8) 长期治理:避免“优化一次,下次又炸”
慢 SQL 告警(耗时+次数)
上线前 SQL 评审 + 压测
容量规划(连接池/线程池/实例规格)
数据归档/冷热分离(控制表膨胀)