用一个案例讲清楚方法论。假设你负责一个订单系统,某天线上反馈"查用户近 30 天订单"的接口从原来的 80ms 涨到了 3 秒,用户开始投诉。
慢查询定位不是玄学,而是一条可重复的流水线:找到语句 → 看执行计划 → 定位根因 → 修复 → 验证。下面按这条线走一遍。
第一步:从慢日志里捞出问题语句
MySQL 有 slow log,TiDB 默认把执行超过 300ms 的语句记入慢日志(tidb-slow.log),也可以在 TiDB Dashboard 的慢查询面板里直接搜索。定位时优先看:执行时间最长的、出现次数最多的、总耗时占比最高的语句——先解决"最贵"的,而不是"最新"的。
这一步的输出是一句具体的 SQL,比如:
SELECT id, order_no, status, amount
FROM orders
WHERE user_id = 12345
AND created_at >= '2026-08-10 00:00:00'
ORDER BY created_at DESC
LIMIT 20;
第二步:EXPLAIN 看执行计划
拿到语句后立刻做 EXPLAIN。重点看三件事:是否全表扫描(type 列出现 ALL)、是否走了错误的索引、预估扫描行数与实际是否差距巨大。
在这个案例里,EXPLAIN 显示 orders 表走了全表扫描,扫描行数约 2000 万——显然有问题。为什么 user_id 上明明有索引却不走?继续看 WHERE 条件发现:user_id 是 VARCHAR 类型,而应用传入的是整数 12345。MySQL 在比较时会把列做隐式类型转换,导致索引失效,退化为全表扫描。
第三步:定位根因并修复
根因确认后,修复往往很小:把参数改成字符串,或者保证类型一致:
SELECT id, order_no, status, amount
FROM orders
WHERE user_id = '12345'
AND created_at >= '2026-08-10 00:00:00'
ORDER BY created_at DESC
LIMIT 20;
改完 EXPLAIN,type 从 ALL 变成 ref,预估扫描行数从 2000 万降到几百行。接口延迟回到 80ms。
常见的四类根因清单
-
缺索引 / 索引失效:隐式类型转换、对索引列使用函数、前导模糊查询(LIKE ‘%xx’)是最常见的三种失效方式。
-
深分页:LIMIT 100000, 20 要扫描前 10 万行再丢弃,正确做法是游标分页或延迟关联。
-
统计信息过期:优化器拿到的统计信息不准,选错执行计划,一条大表 JOIN 瞬间变慢——此时 ANALYZE TABLE 刷新统计信息即可。
-
数据倾斜:某个值占了大半数据(比如"状态=待处理"占了 90%),即使走索引也慢,需要业务上拆分查询条件或异步化。
进阶:TiDB 里怎么定位得更细
在 TiDB 中,EXPLAIN ANALYZE 可以展示每个算子的实际执行时间和行数,把耗时定位到具体算子——是 TableReader 慢、Join 慢,还是取 TSO 慢,一目了然。TiDB Dashboard 的"语句分析"(Statement Analysis)能按 SQL 指纹聚合同类语句,直接看到哪些语句总耗时最高、平均耗时如何变化。
慢查询定位的方法论总结成一句话:**慢日志负责"找出谁慢",执行计划负责"解释为什么慢",修复后必须用 EXPLAIN 和延迟数据双重验证。**工具只是放大器,理解执行计划才是核心能力——这也是从"会 DBA"到"懂数据库"的分水岭。