文章目录
一、开篇暴击:你的数据库正在慢性自杀!
最近排查生产环境时发现,某核心接口的响应时间从200ms飙升到8秒(心跳骤停.jpg)!最终定位到是一条看似简单的查询语句导致的。这让我意识到:不会优化慢SQL的程序员,就像不会踩刹车的赛车手,随时可能车毁人亡!
二、三步定位法:揪出元凶的骚操作
1. 开启慢查询日志(必杀技)
-- 临时开启(重启失效)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒的查询
-- 永久生效配置(my.cnf)
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
2. 日志分析利器(mysqldumpslow)
# 查看最耗时的10条慢查询
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 统计出现次数最多的SQL
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
3. 实时监控大法(SHOW PROCESSLIST)
-- 查看当前正在执行的查询
SHOW FULL PROCESSLIST;
-- 重点关注:
-- Time > 3s 的查询
-- State 显示"Sending data"、"Copying to tmp table"等
-- Info 字段的SQL片段
三、执行计划解剖课(Explain超详解)
案例SQL(用户登录日志表)
SELECT * FROM user_login
WHERE login_time BETWEEN '2023-01-01' AND '2024-01-01'
AND device_type IN (1,2,5)
ORDER BY user_id DESC
LIMIT 100000,10;
EXPLAIN输出解析(重点字段)
字段 | 值 | 诊断结论 | 优化方向 |
---|---|---|---|
type | ALL | 全表扫描(致命伤) | 必须加索引 |
rows | 8534200 | 扫描行数爆炸 | 减少扫描范围 |
Extra | Using filesort | 文件排序(性能杀手) | 优化排序字段索引 |
key | NULL | 没有使用索引 | 创建复合索引 |
四、五大优化绝招(亲测有效)
1. 索引优化组合拳
-- 错误示范(单字段索引)
ALTER TABLE user_login ADD INDEX idx_login_time(login_time);
-- 正确姿势(复合索引)
ALTER TABLE user_login ADD INDEX idx_optimize(login_time, device_type, user_id);
避坑指南:
- 索引字段顺序:范围查询字段放最后(login_time是范围查询)
- 覆盖索引:SELECT字段尽量包含在索引中
- 索引长度:字符串字段使用前缀索引
2. 深分页优化秘技
原SQL的LIMIT 100000,10
会导致:
- 读取100010条数据
- 丢弃前100000条
- 返回最后10条
优化方案:
SELECT * FROM user_login
WHERE id > (SELECT id FROM user_login
WHERE login_time BETWEEN '2023-01-01' AND '2024-01-01'
AND device_type IN (1,2,5)
ORDER BY user_id DESC
LIMIT 100000,1)
ORDER BY user_id DESC
LIMIT 10;
3. 数据类型隐式转换陷阱
-- 表结构:mobile varchar(20)
SELECT * FROM users WHERE mobile = 13800138000; -- 错误!
SELECT * FROM users WHERE mobile = '13800138000'; -- 正确
血泪教训:VARCHAR字段用数字查询会导致:
- 全表扫描
- 索引失效
- 隐性类型转换消耗CPU
4. 巧用覆盖索引
-- 原始查询
SELECT user_name,email FROM users WHERE age > 18;
-- 优化方案
ALTER TABLE users ADD INDEX idx_age_cover(age,user_name,email);
这样只需要扫描索引树,无需回表查数据!
5. 连接查询优化套路
-- 错误示范
SELECT * FROM orders
LEFT JOIN users ON orders.user_id = users.id
WHERE users.register_time > '2023-01-01';
-- 正确姿势
SELECT * FROM users
STRAIGHT_JOIN orders ON users.id = orders.user_id
WHERE users.register_time > '2023-01-01';
为什么有效:
- 小表驱动大表
- 避免LEFT JOIN的默认执行顺序
- 强制指定连接顺序(STRAIGHT_JOIN)
五、实战案例:2秒→0.02秒的奇迹
优化前(执行时间2.3秒):
SELECT product_id,COUNT(*)
FROM order_detail
WHERE create_time > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY product_id
ORDER BY COUNT(*) DESC
LIMIT 10;
优化步骤:
- 添加复合索引:
(create_time, product_id)
- 改写SQL:
SELECT product_id,precalc_count
FROM (
SELECT product_id,COUNT(*) as precalc_count
FROM order_detail
WHERE create_time > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY product_id
) tmp
ORDER BY precalc_count DESC
LIMIT 10;
- 启用查询缓存(适合读多写少场景)
优化结果:执行时间降至23ms!
六、防坑指南(血的教训总结)
- 不要过度索引:每个额外索引增加约10%的写操作开销
- 隐式类型转换:常见于枚举值字段(enum用tinyint代替)
- OR条件陷阱:
WHERE a=1 OR b=2
可以改写成UNION ALL
- 函数操作索引失效:
WHERE YEAR(create_time)=2023
→WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'
- 定期分析表:
ANALYZE TABLE user_login
更新索引统计信息
七、终极武器(自研优化检查清单)
下次优化慢SQL时,按这个checklist逐项检查:
- 是否使用EXPLAIN分析执行计划
- WHERE条件字段是否有合适索引
- 是否出现filesort临时排序
- 是否存在隐式类型转换
- 是否可以使用覆盖索引
- 分页查询是否过深
- 是否必要使用SELECT *
- 是否可以考虑缓存结果
- 是否能够批量操作替代循环
- 是否最新统计信息(ANALYZE TABLE)
八、结语:优化是永无止境的修行
记得去年优化过的一个统计报表SQL,从最初的47秒降到0.5秒,那种成就感就像给老破车换上飞机引擎!但优化不是银弹,要结合业务场景:有时候加个缓存比改SQL更有效,有时候业务逻辑调整能彻底解决问题。
最后送大家一句话:“不会优化的程序员永远在加班,会优化的程序员…也在加班,但至少不用背锅!” (手动狗头)