文章目录
(文末有超实用的排查流程图,建议收藏备用)最近在公司接手了一个性能优化项目,遇到了一个让我头皮发麻的慢查询问题。某个关键接口的响应时间经常突破15秒大关(这谁顶得住啊),经过排查发现罪魁祸首是一个看似简单的统计SQL。今天就带大家完整复盘这次惊心动魄的优化过程,手把手教你如何驯服慢查询!
一、问题现象:用户投诉页面加载转圈圈
市场部的同事反馈:客户画像页面的加载速度越来越慢,特别是在选择「最近30天活跃用户」这个筛选条件时,经常要等十几秒才能出结果(用户都跑光了好吗!!)
使用SHOW PROCESSLIST
查看实时查询,发现有个SQL长期处于"Sending data"状态:
SELECT user_id, COUNT(order_id) AS order_count
FROM user_behavior
WHERE last_active_time > '2023-08-01'
AND status = 1
AND is_deleted = 0
GROUP BY user_id
HAVING order_count > 3
ORDER BY order_count DESC
LIMIT 100;
(冷知识:Sending data状态其实是在读取和发送数据,并不一定是网络问题哦)
二、初诊:EXPLAIN结果让人大跌眼镜
先用EXPLAIN
看看执行计划(这里贴出关键信息):
key | rows | filtered | Extra |
---|---|---|---|
idx_status | 86万 | 10% | Using where; Using filesort |
几个危险信号亮红灯了:
- 扫描行数86万:这得读多少数据啊(硬盘在哭泣)
- filtered只有10%:索引过滤效果差
- Using filesort:内存排序要爆了
- Using temporary:隐形的临时表杀手
三、深度剖析:揪出真正的性能杀手
3.1 索引陷阱大起底
当前索引是(status)
的单列索引,但查询条件还有last_active_time
和is_deleted
。这个索引实际只命中了status字段,其他条件都要回表过滤(血亏!)
更糟糕的是:
last_active_time > '2023-08-01'
这个范围查询直接导致后续索引列失效is_deleted=0
的过滤比status=1更有效(因为90%数据都是status=1)
(划重点:索引字段顺序不对,效果直接打骨折!)
3.2 隐藏的排序灾难
由于要按order_count降序取TOP 100,MySQL不得不:
- 先统计所有用户的订单数
- 在临时表中排序
- 最后取前100条
当用户量达到百万级时,这个临时表可能超过内存限制,触发磁盘排序(速度直接腰斩!)
四、优化三板斧:招招致命!
4.1 索引手术刀:精准打击
新建联合索引:
ALTER TABLE user_behavior ADD INDEX idx_activity (is_deleted, status, last_active_time);
(索引顺序超级重要:高区分度字段在前,等值查询在前,范围查询在后)
4.2 SQL重构:化繁为简
把HAVING条件提前到WHERE:
SELECT user_id, COUNT(order_id) AS order_count
FROM user_behavior
WHERE last_active_time > '2023-08-01'
AND status = 1
AND is_deleted = 0
AND order_id IS NOT NULL -- 隐式转换杀手!
GROUP BY user_id
ORDER BY order_count DESC
LIMIT 100;
(别小看这个AND order_id IS NOT NULL,能避免全表扫描!)
4.3 终极武器:预统计
对于实时性要求不高的统计,使用定时任务预先计算:
CREATE TABLE user_order_stat (
user_id INT PRIMARY KEY,
order_count INT,
update_time DATETIME
);
-- 每天凌晨更新
REPLACE INTO user_order_stat
SELECT user_id, COUNT(*), NOW()
FROM user_behavior
WHERE ...
五、效果验证:质的飞跃!
优化后的EXPLAIN结果:
key | rows | Extra |
---|---|---|
idx_activity | 325 | Using index; Using temporary |
执行时间从15.8秒 → 0.02秒,性能提升790倍!!!(DBA同事直呼内行)
六、慢查询排查标准流程图(建议保存)
START
↓
收到慢查询报警/反馈
↓
█ 捕获问题SQL █
↓
EXPLAIN分析执行计划
↓
→ 检查是否走错索引 → 调整索引顺序/类型
↓ ↓
检查扫描行数 检查Extra信息
↓ ↓
检查where条件 发现Using filesort?
↓ ↓
添加缺失索引 优化排序方式
↓ ↓
█ 验证效果 █ ←←←←←←←←←
↓
█ 持续监控 █
七、血泪经验总结(新人必看!)
- 索引不是越多越好:维护索引也要成本,联合索引比多个单列索引更高效
- 范围查询是索引杀手:尽量把范围查询字段放在联合索引最后
- 警惕隐式转换:字段类型不匹配会导致索引失效(比如字符串存成数字)
- 排序也要命:大数据量排序优先考虑覆盖索引
- 统计类查询可以耍流氓:适当降低实时性要求换取性能
(最后送大家一句话:优化永无止境,但要避免过度优化!)
下次遇到慢查询时,记得按这个流程一步步排查。如果还是搞不定…欢迎在评论区留言求救!(记得脱敏你的SQL哦~)