案例:业务卡顿,用 dbskiter 5 分钟定位慢查询¶
场景背景¶
- 时间:2026-06-10 周二 14:30
- 现象:客服反馈系统响应慢,用户查询订单页面加载超过 8 秒
- 数据库:MySQL 8.0.32,业务库
jump - 环境:开发团队无专职 DBA,由后端工程师兼管数据库
生活化比喻:数据库就像厨房,订单查询是高峰期点单,慢查询就是厨师(MySQL)在一大堆食材(数据)里翻找,没找到合适的工具(索引),导致上菜慢。
排查步骤¶
1. 先看整体健康状态(30 秒)¶
像体检先做血常规,排查先看数据库整体指标:
dbskiter --database=jump monitor health
关键输出:
健康评分: 66.4/100(需要关注)
严重问题: 1
高风险问题: 3
重点关注: - 临时表磁盘使用率:61.96%(警告) - 行锁等待次数:147(警告) - 排序合并次数:5753(警告)
解读:这三项指标同时偏高,通常意味着有大量复杂查询在内存里排不下,落到磁盘排序,同时触发了锁竞争。这就像是厨房里既有人在翻箱倒柜(全表扫描),又有人在排队等同一个砧板(锁等待)。
2. 抓取慢查询(1 分钟)¶
直接定位"最慢的厨师":
dbskiter --database=jump diagnose slow-queries
发现 Top 1 慢查询:
SELECT o.*, u.username, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
AND o.create_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY o.create_time DESC
LIMIT 100;
| 指标 | 数值 | 含义 |
|---|---|---|
| 执行时间 | 3.8s | 远超用户忍耐极限(1s) |
| 扫描行数 | 1,247,000 | 全表扫描,没走索引 |
| 临时表 | 磁盘临时表 | 内存 tmp_table_size 不够,落盘排序 |
| 排序方式 | filesort | 无索引可用,手动排序 |
3. 分析执行计划(2 分钟)¶
用 dbskiter 的 SQL 审核功能看 MySQL 是怎么执行这条语句的:
dbskiter --database=jump audit sql "
SELECT o.*, u.username, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
AND o.create_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY o.create_time DESC
LIMIT 100;
"
审核结果:
[WARNING] 全表扫描 detected: orders 表扫描 1,247,000 行
[WARNING] 缺少索引: orders.status + orders.create_time 联合索引
[INFO] JOIN 使用索引: users.id(主键)
[WARNING] SELECT * 返回 28 列,实际可能只需 4 列
4. 推荐索引与优化(1 分钟)¶
dbskiter --database=jump audit recommend-indexes "
SELECT o.*, u.username, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
AND o.create_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY o.create_time DESC
LIMIT 100;
"
推荐方案:
-- 方案A:覆盖索引(推荐)
CREATE INDEX idx_orders_status_ctime ON orders(status, create_time, user_id);
-- 方案B:如需覆盖 SELECT * 的列,可用覆盖索引
CREATE INDEX idx_orders_covering
ON orders(status, create_time, user_id, id, amount, status, create_time);
原理说明:索引就像书的目录,把
status和create_time排好序,MySQL 直接翻到对应章节,不用逐页翻(全表扫描)。
解决方案与验证¶
执行优化¶
-- 步骤1:创建索引(业务低峰期执行,如凌晨)
CREATE INDEX idx_orders_status_ctime ON orders(status, create_time);
-- 步骤2:改写 SQL,减少返回列(减轻网络传输)
SELECT o.id, o.amount, o.status, o.create_time, u.username, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
AND o.create_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY o.create_time DESC
LIMIT 100;
验证效果¶
dbskiter --database=jump audit sql "
SELECT o.id, o.amount, o.status, o.create_time, u.username, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
AND o.create_time > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY o.create_time DESC
LIMIT 100;
"
优化后指标:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 执行时间 | 3.8s | 0.04s | 95x |
| 扫描行数 | 1,247,000 | 1,200 | 99.9% |
| 临时表 | 磁盘 | 内存 | 避免磁盘 IO |
| 索引使用 | 无 | idx_orders_status_ctime | 覆盖查询 |
复盘要点¶
1. 排查口诀(记住这个顺序)¶
业务慢 → 看健康 → 抓慢查 → 看执行计划 → 加索引 → 验证
2. 关键判断指标¶
- 扫描行数 / 返回行数 > 100:大概率没走索引,要优化
- 临时表落盘:
tmp_table_size不够或查询太复杂 - filesort:
ORDER BY字段没索引
3. 常见误区¶
| 误区 | 正确做法 |
|---|---|
| "执行时间 3 秒不算慢" | 看扫描行数,100 万行扫描就算慢 |
| "SELECT * 方便" | 只取需要的列,减少 IO 和网络传输 |
| "加个单列索引就行" | 联合索引(status + create_time)比单列索引高效 |
| "索引越多越好" | 索引增加写入成本,按需创建,定期清理冗余索引 |
4. 后续预防¶
# 启用慢查询日志,长期监控
dbskiter --database=jump inspector run --type performance
# 每周巡检一次,提前发现性能退化
dbskiter --database=jump inspector report --output weekly_report.html
延伸阅读¶
- 案例:CPU 飙升根因分析
- 案例:磁盘空间告警
- dbskiter 命令:
diagnose slow-queries、audit sql、audit recommend-indexes