Article
实战演练:一次博客列表慢查询的治理全过程
实战演练:一次博客列表慢查询的治理全过程
理论文章告诉你要看 EXPLAIN;真正有用的是把一次完整治理走完:发现 → 复现 → 解释 → 改索引/改 SQL → 验证 → 观察写入副作用。下面用「博客已发布列表」这类高频查询当标本,按可照做的步骤写。
场景
接口:GET /api/articles?page=1&page_size=10
业务 SQL 近似:
SELECT id, title, slug, summary, cover_image, published_at
FROM articles
WHERE status = 'published'
ORDER BY published_at DESC, id DESC
LIMIT 10 OFFSET 0;
当文章到几千、几万行,或 SELECT * 误拉了 content 长文本,这条会开始抖。即便表还不大,在开发环境把慢查询阈值调低,也能提前暴露坏形状。
第 1 步:把它变成可见问题
临时打开慢查询(开发或可接受的窗口):
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.2; -- 开发可更低
再用压一下列表接口:
for i in $(seq 1 20); do
curl -s -o /dev/null -w "%{time_total}\n" \
"http://127.0.0.1:18080/api/articles?page=1&page_size=10"
done
到慢日志里捞出真实 SQL。注意 ORM 生成的语句可能带多余列或额外 COUNT;列表页若每次都 SELECT count(*) 全表统计,也可能是另一条慢点。
第 2 步:EXPLAIN,先读懂再动手
EXPLAIN
SELECT id, title, slug, summary, cover_image, published_at
FROM articles
WHERE status = 'published'
ORDER BY published_at DESC, id DESC
LIMIT 10;
对照:
| 现象 | 含义 | 优先动作 |
|---|---|---|
type=ALL | 全表扫 | 考虑加联合索引 |
key=NULL | 没用上索引 | 检查条件是否包了函数 |
Using filesort | 额外排序 | 让索引覆盖 ORDER BY |
rows 很大 | 预估扫描多 | 收紧条件或改索引顺序 |
若只有 status 单列索引,选择性又差,优化器仍可能全表扫后排序——这是「有索引但没用」的典型现场。
第 3 步:改查询形状(先免费优化)
在加索引前,先修应用层:
- 列表禁止选
content。正文只在详情接口取。 - Preload 标签,消灭 N+1。
- 分页排序稳定:
ORDER BY published_at DESC, id DESC。 - COUNT 策略:总页数若不是强需求,可考虑延迟计数或缓存;不要为装饰性页码付全表 COUNT。
常常只做完这一步,体感已经好多了。索引是放大正确查询的工具,不是掩盖坏查询的创可贴。
第 4 步:加一条对症联合索引
对上面的列表形状,常见有效索引是:
ALTER TABLE articles
ADD INDEX idx_articles_status_published_id (status, published_at, id);
原则:等值列 status 在前,排序列在后。若你强依赖分类页:
WHERE category_id = ? AND status = 'published'
ORDER BY published_at DESC
再评估 (category_id, status, published_at),而不是再单独堆一个低质量 status 索引。
加索引前备份,或在从库/副本验证。生产大表要看 DDL 是否锁写、是否该低峰执行。
第 5 步:用前后对比验收
同一环境、同一数据量:
EXPLAIN ... -- key 是否变成新索引,rows 是否下降
再用接口测耗时分布(均值/中位数即可)。通过标准建议写死:
- 计划走新索引
- P95 耗时下降(例如从 80ms → 15ms,数字按你环境)
- 发布文章、更新文章的写入耗时没有明显恶化
若计划变好但耗时不变,检查是不是瓶颈在应用 N+1、网络或前端,而不是这条 SQL。
第 6 步:清理与防回归
- 删除确认无用的重复索引
- 在 PR 里贴 EXPLAIN 对比截图或文本
- 给列表 repository 加注释:
# depends on idx_articles_status_published_id - 慢查询阈值恢复生产合理值,并确保日志轮转
可选:在测试里固定一篇「列表查询 SQL」断言不含 content 字段——防止哪天图省事又 Select("*")。
现场记录模板(建议直接复制)
日期:
现象:列表接口 P95 =
原始 SQL:
EXPLAIN 摘要:
已做修改:
新索引:
验收数据:
回滚方式:DROP INDEX ...
个人项目也值得留这种半页纸记录。三个月后表大了,你还能知道当初为什么留下这条索引。
小结
慢查询治理是闭环,不是「补丁式加索引」。对博客列表:先裁列、先消 N+1、再按 WHERE + ORDER BY 做联合索引,最后用 EXPLAIN 与耗时对比验收。把这一次演练做扎实,下次换归档页、后台筛选,你只是换标本,步骤不用重学。