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 治理完下周又回来——因为修复的索引被后续的某个变更高估效果或者被回滚。复核对不对,治理就永远在原地打转。

延伸阅读