文章目录
一、开篇暴击:那些年我们等过的7秒
(说好的用户体验呢?!)去年接手一个电商项目时,我遇到了职业生涯最棘手的SQL性能问题——用户订单列表查询竟然要7秒!用户投诉电话像催命符一样,CTO直接在我工位后边建了个"观光区"。经过两周地狱式优化,最终把响应时间压缩到0.01秒。今天就把这波实战经验掏心窝子分享给大家!
二、定位问题:慢查询的三大元凶
1. 执行计划解密(看这个就够了!)
EXPLAIN SELECT * FROM orders
WHERE user_id=123
AND create_time BETWEEN '2023-01-01' AND '2023-12-31'
ORDER BY total_price DESC
LIMIT 20;
重点看这几个字段:
- type:ALL就是全表扫描(直接判死刑!)
- rows:扫描行数超过1000就要警惕
- Extra:Using filesort出现=性能炸弹
2. 慢查询日志的正确打开方式
(配置错了等于白干!)
# my.cnf配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1 # 超过1秒的查询都记录
log_queries_not_using_indexes = 1 # 没走索引的查询
推荐神器:
- pt-query-digest:分析慢查询日志(DBA都在用!)
- MySQL Workbench:可视化执行计划(小白友好)
三、优化三板斧(立竿见影!)
1. 索引优化实战技巧
-- 错误示范:无索引查询
SELECT * FROM products WHERE category = '电子' AND price > 1000;
-- 正确姿势:联合索引
ALTER TABLE products ADD INDEX idx_cat_price (category, price);
(血泪教训啊朋友们!)
- 最左前缀原则:
(a,b,c)
索引能命中a
/a,b
/a,b,c
查询 - 索引选择性:选择区分度高的字段在前
- 覆盖索引:查询字段全在索引中(Extra显示Using index)
2. 改写SQL的骚操作
-- 原SQL(执行时间2.8秒)
SELECT * FROM orders
WHERE status = '待支付'
AND create_time > DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 优化版(0.3秒!)
SELECT * FROM orders FORCE INDEX(idx_status_time)
WHERE status = '待支付'
AND id > (SELECT id FROM orders
WHERE status = '待支付'
ORDER BY id DESC
LIMIT 100000,1)
(这波分页优化直接起飞!)
3. 架构层面的降维打击
当单表数据突破500万时:
- 垂直拆分:把text/blob字段拆分到副表
- 水平拆分:按时间/地域分表
- 冷热分离:3个月前的订单归档
- 查询缓存:Redis缓存热点数据(并发量高时慎用!)
四、经典案例:7秒变0.01秒的奇迹
原SQL分析
SELECT u.name, o.order_no, p.product_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id
WHERE u.vip_level > 3
AND o.create_time BETWEEN '2023-01-01' AND '2023-12-31'
ORDER BY o.total_price DESC
LIMIT 100;
执行时间7.2秒,执行计划显示:
- 三表全表扫描
- Using temporary
- Using filesort
优化步骤
- 索引手术:
ALTER TABLE users ADD INDEX idx_vip(vip_level);
ALTER TABLE orders ADD INDEX idx_user_time(user_id, create_time);
ALTER TABLE products ADD INDEX idx_id_name(id, product_name);
- SQL重构:
SELECT
u.name,
o.order_no,
(SELECT product_name FROM products WHERE id=o.product_id) AS product_name
FROM users u
STRAIGHT_JOIN orders o ON u.id = o.user_id
WHERE u.vip_level > 3
AND o.create_time BETWEEN '2023-01-01' AND '2023-12-31'
ORDER BY o.total_price DESC
LIMIT 100;
- 业务妥协:
- 去掉实时性要求,改为定时统计
- 分页限制最多查看100页
- 增加total_price的单独索引
优化结果
响应时间从7.2秒 → 0.01秒!查询效率提升700倍!
五、防坑指南(都是踩过的雷!)
-
索引失效的7种死法:
- 对索引字段做运算
- 使用前模糊查询(like ‘%xxx’)
- 隐式类型转换(varchar传了数字)
- 索引字段使用函数
- or条件未全覆盖
- 最左前缀缺失
- 不同排序字段混合使用
-
执行计划中的死亡信号:
- type=ALL(全表扫描)
- rows>10000
- Extra出现Using temporary
- key_len过小
-
MySQL配置的魔鬼细节:
innodb_buffer_pool_size = 物理内存的70% max_connections = 实际需要+20% thread_cache_size = 8 + (max_connections)/100
六、终极武器:持续优化体系
-
监控报警系统:
- Prometheus + Grafana 实时监控
- 慢查询阈值报警(超过500ms立即通知)
-
自动化分析流程:
-
定期健康检查:
- 每周执行一次
OPTIMIZE TABLE
- 每月分析一次索引使用率
- 每季度做一次全量SQL审计
- 每周执行一次
七、写在最后
SQL优化就像侦探破案,需要:
- 细看执行计划(放大镜)
- 分析慢查询日志(监控录像)
- 了解业务场景(作案动机)
- 熟悉数据结构(案发现场)
记住:没有最好的优化方案,只有最适合业务场景的解决方案。当你觉得优化到极限时,试试这三个终极大招:
- 加内存(简单粗暴但有效)
- 换SSD(速度提升立竿见影)
- 业务逻辑重构(釜底抽薪)
(别问我怎么知道的,都是泪…)下次遇到慢查询,记得先深呼吸,然后按这个checklist一步步来,你也能成为同事眼中的"优化之神"!