MySQL 慢查询日志里躺了三个月的 "1.2s 常客",被我三行代码送进了养老院
上周翻监控面板,发现有个接口平均响应 1.8 秒,P99 直接飙到 8 秒。用户没炸,我自己先炸了——这玩意儿是首页核心数据,每天被调几十万次。
先别急着上 Redis,我习惯先拿 `EXPLAIN` 看看这 SQL 到底在干嘛。一看 `type: ALL`,`rows: 187w`,好家伙,全表扫描。再瞅眼 `WHERE` 条件,是个 `DATE(created_at) = '2024-09-10'` 的写法。MySQL 看到这种函数套字段,索引直接摆烂,因为它没法预判 `DATE()` 之后的结果分布。
改了两处:一是把查询改成 `created_at >= '2024-09-10 00:00:00' AND created_at < '2024-09-11 00:00:00'`,二是给 `created_at` 补了单列索引。上线后那条 SQL 从 1.2s 掉到 8ms,监控曲线陡得跟悬崖似的。
但索引不是万能药。另一个列表接口,字段都走了索引,文件排序(`Using filesort`)还是拖慢了排序。加了个覆盖索引 `(status, created_at, id)`,让查询和排序都在索引里完成,回表都省了。这里踩了个小坑:覆盖索引字段顺序得讲究,把 `status` 这种区分度高的放前面,过滤完剩的数据少了,再排序就轻快。
缓存这块我之前很激进,啥都往 Redis 塞,结果内存告警比接口还频繁。现在我的原则是"读多写少、计算昂贵、容忍秒级延迟"才进缓存。有个用户统计面板,数据实时性要求不高,但聚合 SQL 要扫两三张表。我改成定时任务每 5 分钟写一次 Redis,接口直接读缓存,QPS 从 200 撑到 4000 都没喘粗气。
静态资源倒是老生长谈,但很多人漏了个细节:CDN 回源策略。我把 OSS 绑到 CDN 后,没开 `过滤参数`,导致带 `?v=1.2.3` 的版本号和裸 URL 被当成两个资源缓存,命中率直接腰斩。勾上这个选项,再配合 HTML 里资源 URL 的 hash 指纹,缓存策略才算闭环。
还有个小技巧,宝塔 Nginx 里开 `gzip_static` 时,记得提前用 `gzip` 命令压好 `.gz` 文件放同级目录。Nginx 直接读预压缩文件,比实时压缩省 CPU,高并发时这点开销攒起来很可观。我用脚本批量处理了一遍静态资源,CPU 占用降了 15% 左右。
优化完这轮,我给自己定了个规矩:先抓慢查询日志里的 TOP10,再碰缓存和 CDN。基础 SQL 没理顺,上层堆再多技术都是给烂楼刷漆。你们有没有那种"改一行代码,监控图当场变脸"的经历?

