深夜救火:订单列表页转了 8 秒
咖啡馆打烊前,老王拎着笔记本冲进来:他那个电商项目,订单列表页从上周开始越来越慢,现在平均 8 秒才出数据,客诉截图都攒了一沓。我把咖啡推到一边,说了句 DBA 的第一原则:别猜,先测量。优化 SQL 之前,先让慢 SQL 自己站出来——这一站,就是慢查询日志和 EXPLAIN。
第一步:开慢查询日志,把真凶抓出来
MySQL 默认不记录慢 SQL,得手动开。三个参数搞定:开关、阈值、以及要不要把没走索引的 SQL 也一并记录:
# 动态开启(重启失效,my.cnf 里补一份持久化)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
# 没走索引的 SQL 也记录(表数据大时日志会很吵,按需开)
SET GLOBAL log_queries_not_using_indexes = ON;
# my.cnf 持久化写法
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1阈值给 1 秒比较务实:线上业务超过 1 秒的 SQL 基本都值得优化,设太低日志会被海量 0.1 秒的查询淹没。开上几分钟后,慢日志里果然出现了那个熟悉的身影:
# Time: 2026-09-14T22:31:05
# Query_time: 8.21 Lock_time: 0.000 Rows_sent: 20 Rows_examined: 3814592
SELECT o.id, o.order_no, o.amount, u.nickname
FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.user_id = 9527 AND o.status = 1
ORDER BY o.create_time DESC LIMIT 20;慢日志这行头信息信息量极大:Rows_sent 是返回给客户端的行数,Rows_examined 是引擎实际扫过的行数。这条 SQL 返回 20 行,却扫了 381 万行——两者比值就是效率的体温计,正常应该在个位数到几十之间,这里高达 19 万倍,典型的大海捞针。
第二步:EXPLAIN,给 SQL 装上行车记录仪
知道哪条 SQL 慢还不够,还得知道它为什么慢。在 SQL 前面加个 EXPLAIN,MySQL 不执行查询,只返回执行计划:
EXPLAIN
SELECT o.id, o.order_no, o.amount, u.nickname
FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.user_id = 9527 AND o.status = 1
ORDER BY o.create_time DESC LIMIT 20;输出十来列,入门先盯五个关键列:
| 列 | 含义 |
|---|---|
| type | 访问类型,从好到差:const > eq_ref > ref > range > index > ALL |
| possible_keys / key | 候选索引 / 实际用上的索引,key 为 NULL 就是没走索引 |
| rows | 预估扫描行数,判断成本的核心指标 |
| key_len | 实际用到的索引长度,判断联合索引用了几列 |
| Extra | 附加动作,出现 Using filesort、Using temporary 就要警惕 |
老王这条 SQL 的执行计划,坏消息全占了:
| 列 | 值 | 解读 |
|---|---|---|
| type | ALL | 全表扫描,最差的一档 |
| possible_keys | NULL | 优化器没找到可用索引 |
| key | NULL | 一个索引都没用 |
| rows | 3814592 | 预估扫 381 万行 |
| Extra | Using where; Using filesort | 过滤完还得额外排序 |
诊断结论一句话:orders 表上没有任何可用索引,WHERE 条件全靠逐行过滤,ORDER BY 还要把结果集拉去排序。8 秒,是它应得的。
第一刀:给 WHERE 和 ORDER BY 建索引
WHERE 用到 user_id、status,ORDER BY 用到 create_time——一把联合索引把三列全收编,顺序的讲究(为什么是这个顺序)下一篇细讲,先看效果:
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);再跑一次 EXPLAIN,画风突变:type 变成 ref(常数查找),key 用上了新索引,rows 从 381 万降到 42,Extra 里出现 Backward index scan——索引本身按 create_time 有序,倒着扫就能直接取最新的 20 条,filesort 消失了。线上实测:8.21 秒 → 30 毫秒。
老王千恩万谢地走了,但这只是入门。今天用到的 type、rows、Extra 都还有大量细节没展开:type 每一档的准确含义?key_len 怎么算?Using index 和 Using index condition 是不是一回事?这些暗语不弄懂,EXPLAIN 看了等于白看——下一篇就把这十来列逐一精读。
咖啡凉了,记得趁热喝。
评论 (0)