实战演练:一次博客列表慢查询的治理全过程

Article

实战演练:一次博客列表慢查询的治理全过程

管理员技术分享57 阅读

实战演练:一次博客列表慢查询的治理全过程

理论文章告诉你要看 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 步:改查询形状(先免费优化)

在加索引前,先修应用层:

  1. 列表禁止选 content。正文只在详情接口取。
  2. Preload 标签,消灭 N+1。
  3. 分页排序稳定ORDER BY published_at DESC, id DESC
  4. 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 与耗时对比验收。把这一次演练做扎实,下次换归档页、后台筛选,你只是换标本,步骤不用重学。

评论

暂无评论,来聊聊这篇文章吧。