4.3 慢查询定位:执行计划
分库之后定位慢查询反而更麻烦了:一条慢 SQL 可能只在某个分片上慢,可能只在特定参数下慢,传统的一台机器看日志完全行不通。这一节讲我们建立在 CLS 之上的慢查询定位体系,以及如何用执行计划把「为什么慢」回答到行级。
场景:分库后的慢查询长什么样
分库后的慢查询有三种新形态。第一种是「单片慢」:同一个 SQL 在 15 个分片上都快、只在第 7 号分片上慢——通常是数据倾斜或热分区。第二种是「参数敏感」:SQL 本身不慢,但特定订单号(比如大商家的订单关联了一堆明细)会触发深分页。第三种是「跨库归并慢」:SQL 在每个分片都很快,但 16 个分片的结果归并耗时占了大头。
| 慢查询形态 | 特征 | 排查入口 |
|---|---|---|
| 全片慢 | 所有分片都慢 | 看执行计划 |
| 单片慢 | 只一个分片慢 | 看分片数据量与热点 |
| 参数敏感 | 特定参数才慢 | 找参数规律与数据分布 |
| 归并慢 | 单片快整体慢 | 看网络与归并代码 |
| 锁等待慢 | 慢但 SQL 简单 | 看行锁与事务 |
方案:CLS 统一慢日志加执行计划评审
CLS 把 16 个分片的慢日志归并到同一主题,带分片编号的标签(1.4 节提到过)。每周五的慢查询评审会上,跑一条检索语句就能拿出 Top 慢 SQL 榜:按「扫描行数乘以执行次数」排序而不是按单次耗时——一条跑 500 次每次 1 秒的 SQL 比一条只跑一次的 10 秒 SQL 危险得多。
| 评审指标 | 含义 | 阈值 |
|---|---|---|
| 总扫描行 | 扫描行数乘以执行次数 | 大于 1000 万 |
| 单次耗时 p99 | 接口级别 | 大于 500 毫秒 |
| 索引命中率 | 查询走的索引 | 无索引直接 P0 |
| 深分页偏移 | LIMIT 起始值 | 大于 10 万 |
| 临时表与文件排序 | 执行计划 Extra 列 | 出现即记录 |
评审会的产出是「治理任务表」,每条慢 SQL 落一个负责人与修复窗口。这套机制运行两年,慢查询从每周 40 条降到每周 3 条以内,是本书里性价比最高的一件事。
CLS 检索语句备忘
评审会用的检索语句本身也值得沉淀。下面三条是点击率最高的检索模式,按「扫描行乘以执行次数」排序的语句只在几分钟内就跑完,比 grep 16 台机器快两个量级:
| 检索目的 | CLS 检索语句要点 |
|---|---|
| 本周慢 SQL 榜 | 按扫描行数降序聚合 SQL 指纹 |
| 单片慢定位 | 过滤分片编号标签按耗时排序 |
| 公共受害者模式 | 按等待行锁的 SQL 指纹聚合计数 |
| 参数敏感型 | 按同一指纹的耗时方差排序 |
第四条「按耗时方差排序」是后期发现的技巧:同一 SQL 指纹的耗时方差大,说明它对参数敏感,这类 SQL 在平均榜上不显眼但偶发拖慢接口。方差比均值更能暴露参数敏感问题。
执行计划四看法是定位单条 SQL 的基本功,评审会上每个人都练成了肌肉记忆。这套「四看法」是实践打磨出来的、经过上百条慢 SQL 验证的,比一上来就看成本列(filtered)的新手路线更聚焦——成本列受统计信息影响大,容易误导。
配置:执行计划四看法
定位单条慢 SQL 的标准动作是看执行计划。我们总结了「四看法」:看访问类型(type)、看可能的键(possible_keys)、看扫描行数(rows)、看额外信息(Extra)。
-- 先看分片内执行计划
EXPLAIN SELECT * FROM t_order
WHERE shop_id = 10086 AND status = 1
ORDER BY create_time DESC LIMIT 20;
最典型的坏信号是 type 为 ALL(全表扫描)或者 Extra 出现 Using filesort(文件排序)。上面这条语句如果只有 (shop_id) 单列索引,排序必然走文件排序——改成联合索引 (shop_id, status, create_time) 后,排序与过滤都能走索引。联合索引的顺序设计在 4.4 节专门展开。
分库场景下还要多看一层:ShardingSphere 的执行日志会打印实际下推到每个分片的 SQL。有时候逻辑 SQL 带了无法路由的条件,被改写成全片广播——日志里一目了然。
分库慢查询排查还有个专用技巧:先看「分片编号分布」。一条慢 SQL 在 CLS 里如果只在 ds_03 标签下出现,那它八成是「单片慢」形态,直接去对应分片看数据量与热点;如果 16 个分片标签都有,则是全片慢,看执行计划。这个标签过滤能节省一半以上的定位时间,是 CLS 归集架构的直接红利。
踩坑:慢日志阈值设太高
最初 long_query_time 沿用 1 秒的默认值,结果大量「500 到 900 毫秒」的慢 SQL 漏在阈值外面,接口 p99 报警了却找不到对应 SQL。把阈值降到 0.5 秒后慢日志量涨了 8 倍,但定位效率反而高了——因为榜上的 SQL 真的就是问题所在。慢日志宁多勿少,磁盘成本远高于排查成本。
第二个坑是 EXPLAIN 看到的是估算。执行计划里的 rows 是优化器估算值,真实扫描行可能差几倍。判断真实行数要用 EXPLAIN ANALYZE(MySQL 8.0 加)或者打开 trace 看实际执行。几次诡异的「执行计划显示只扫 100 行实际跑 5 秒」,最后都是靠 EXPLAIN ANALYZE 定位的。
第三个坑是「慢 SQL 会传染」。一条慢 SQL 长时间持有行锁,会拖慢后面所有等锁的查询,慢日志里瞬间出现一堆「受害者 SQL」。评审排序时先找「公共受害者模式」:多条慢 SQL 等同一行锁,根因往往只有一条 SQL。
第四个坑是「归并层慢」被误判成 SQL 慢。16 个分片各自都 50 毫秒,但接口 p99 是 800 毫秒——耗时全花在归并代码里(内存排序加网络)。这类问题看分片慢日志永远找不到,要看应用侧的归并耗时埋点。我们在归并代码里加了分段计时(网络段、归并段、渲染段)后才定位清楚。
评审会运转机制
评审会能坚持两年,靠的不是纪律而是形式足够轻。每周五下午半小时,固定席位是 DBA、后端 owner 与一名轮值开发,流程只有三步:过榜、领任务、验收上周任务。不开大会、不写长文档,任务表上一行字一条。
| 评审环节 | 时长 | 产出 |
|---|---|---|
| 过本周慢 SQL 榜 | 10 分钟 | Top5 进入讨论 |
| 领修复任务 | 10 分钟 | 任务表更新 |
| 验收上周任务 | 10 分钟 | 闭环或升级 |
机制虽轻,但有一条硬规矩:P0 级慢 SQL(无索引或全扫)当周必须修,修不完的升级到技术负责人。治理一旦允许「下次再说」,就会积累到无人能修的程度。
小结
慢查询治理体系=CLS 统一归集加总分榜加每周评审会加执行计划四看法。指标上用「扫描行乘以执行次数」排序、阈值宁低勿高、执行计划看估算也要看真实值。分库特有的三类慢(单片慢、参数敏感、归并慢)各有对应排查入口。治理贵在坚持成机制,一次性优化治标,常态化评审治本。还有一个经验:慢查询治理要给开发同学留「自首通道」。我们规定评审会上主动报慢 SQL 的不扣绩效、被 CLS 抓到才扣,实施后主动上报率提高了四倍,很多藏在角落的慢 SQL 在造成事故前就被消化掉了,这是评审会之外最有价值的补充机制。
操作清单:慢查询从发现到修复
| 步骤 | 动作 | 标准 |
|---|---|---|
| 1 检索 | CLS 拉本周慢 SQL 榜 | 按扫描行乘以次数排序 |
| 2 分类 | 判定慢的形态 | 全片、单片、参数、归并 |
| 3 计划 | 跑 EXPLAIN 加 ANALYZE | 四法看全 |
| 4 修复 | 加索引、改写、分页改写 | 灰度验证 |
| 5 复核 | 下周评审会回看 | 跌出 Top 榜才算闭环 |
闭环标准很重要。我们曾经只做前四步,结果慢 SQL 治理完下周又回来——因为修复的索引被后续的某个变更高估效果或者被回滚。复核对不对,治理就永远在原地打转。
延伸阅读
- 腾讯云架构实践:CLS 日志服务的接入与检索语句参考
- go 实战:爬虫系统:高吞吐访问下的采样与性能定位方法
- node 实战:看板系统:监控看板背后的数据采集与慢查询联动
- MySQL 实战:分库分表订单系统:回到本书,4.4 节讲索引优化
