🔍 慢 SQL 分析与 SQL 优化实战指南、
🧠前言
在数据库性能调优中,慢 SQL 是性能瓶颈的常见元凶。
一次慢查询可能会拖垮整个业务线程池,甚至引发锁等待、雪崩效应。
对后端开发与 DBA 而言,快速定位并优化慢 SQL,能显著提升系统的吞吐量与稳定性。
优化的价值:
- 提升查询响应速度
- 降低数据库 CPU/IO 消耗
- 提高并发承载能力
- 减少死锁与锁等待概率
一、慢 SQL:性能杀手
💡 慢 SQL 定义与成因
⚠️ 常见慢 SQL 场景
场景 | 表现 | 影响 |
---|---|---|
全表扫描 | rows 值巨大 | CPU/IO 飙升 |
锁等待 | 事务阻塞 | 响应延迟 |
排序临时表 | Using temporary | 内存溢出 |
深度分页 | LIMIT 100000,10 | 扫描全表 |
什么是慢查询
MySQL 定义慢查询为:
执行时间超过 long_query_time(默认 10 秒)的 SQL,并且没有命中慢日志白名单。
常见导致慢 SQL 的场景
1.索引失效:查询条件未命中索引,或索引被错误使用。
2.锁等待:事务持有锁时间过长,导致后续 SQL 阻塞。
3.数据量暴增:表行数增长导致原本的 SQL 变慢。
4.执行计划错误:优化器选择了低效索引或全表扫描。
5.复杂 JOIN:大表关联且缺乏合适索引。
6.分页深度过大:LIMIT offset, size 在高 offset 下性能极差。
7.函数或类型转换:在索引列上做计算导致无法走索引。
二、慢查询定位技巧
⚙️ 开启慢日志
-- 查看慢日志配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 动态设置(重启失效)
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1; -- 单位:秒
-- 永久配置 my.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
🔧 日志分析工具
# mysqldumpslow 基础分析
mysqldumpslow -s t /var/log/mysql/slow.log
# pt-query-digest 高级分析
pt-query-digest /var/log/mysql/slow.log
# 输出示例
# Rank Response time Calls Query
# ==== ============= ===== =====
# 1 112.2345s 25 SELECT * FROM orders WHERE ...
三、SQL 调优误区
⚠️ 常见优化陷阱
误区 | 问题 | 正确做法 |
---|---|---|
盲目加索引 | 索引过多影响写性能 | 分析查询模式 |
**SELECT *** | 网络传输/内存浪费 | 按需取字段 |
LIMIT 深分页 | 扫描全表 | 使用游标分页 |
OR 条件 | 索引失效 | 改用 UNION |
函数操作列 | 索引失效 | 计算移到应用层 |
💡 分页优化对比
-- 低效深分页
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10; -- 扫描1000010行
-- 高效游标分页
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10; -- 扫描10行
四、Explain 执行计划解密
💡 Explain 关键字段解读
字段 | 含义 | 优化目标 |
---|---|---|
type | 访问类型 | 至少达到 range |
key | 实际使用索引 | 避免 NULL |
rows | 预估扫描行 | 减少到1% |
Extra | 附加信息 | 避免 Using filesort |
📊 执行计划表示例
id | select_type | table | type | key | rows | Extra |
---|---|---|---|---|---|---|
1 | SIMPLE | users | ref | idx_age | 100 | Using where |
⚠️ 危险信号
- type=ALL:全表扫描
- Using temporary:临时表
- Using filesort:文件排序
- rows > 10000:大表扫描
五、联合索引优化实战
💡 最左前缀原则
⚙️ 案例:电商订单查询优化
问题 SQL:
SELECT * FROM orders
WHERE user_id = 100
AND status = 'completed'
AND create_time > '2023-01-01'
ORDER BY amount DESC;
优化前:
指标 | 值 |
---|---|
执行时间 | 2.3s |
扫描行数 | 120,000 |
索引 | NULL |
优化方案:
-- 创建联合索引
ALTER TABLE orders ADD INDEX idx_user_status_time(user_id, status, create_time);
-- 改写 SQL
SELECT * FROM orders
WHERE user_id = 100
AND status = 'completed'
AND create_time > '2023-01-01'
ORDER BY amount DESC;
-- 注意:amount 排序需单独处理
优化后:
指标 | 值 | 提升 |
---|---|---|
执行时间 | 0.05s | 46倍 |
扫描行数 | 350 | 342倍 |
索引 | idx_user_status_time |
六、SQL 优化流程
🔄 优化四步法
⚙️ 优化策略优先级
- 索引优化:80% 问题解决方案
- SQL 改写:调整查询逻辑
- 表结构调整:分区/分表/归档
- 架构升级:读写分离/缓存
📈 优化效果对比
案例 | 优化前 | 优化后 | 提升 |
---|---|---|---|
订单查询 | 1200ms | 25ms | 48倍 |
用户分页 | 850ms | 8ms | 106倍 |
报表统计 | 15s | 0.8s | 18倍 |
七、实战经验与建议
🛡️ 慢 SQL 预防体系
⚡️ 监控命令速查
-- 实时进程
SHOW FULL PROCESSLIST;
-- 锁等待
SELECT * FROM sys.innodb_lock_waits;
-- 索引统计
SHOW INDEX FROM orders;
-- 表状态
SHOW TABLE STATUS LIKE 'orders';
📝 SQL 开发规范
- 禁止:SELECT * / 大事务 / 全表更新
- 必须:WHERE 条件索引 / LIMIT 限制
- 建议:事务 < 100ms / 单表 < 500w行
- 强制:线上 SQL 必须评审
八、总结
🏆 优化黄金法则
📚 推荐工具清单
工具 | 用途 | 推荐场景 |
---|---|---|
pt-query-digest | 慢日志分析 | 定期巡检 |
sys schema | 性能视图 | 实时监控 |
EXPLAIN FORMAT=JSON | 执行计划 | 深度分析 |
Percona Toolkit | 运维套件 | 专业DBA |
数据驱动优化:没有指标不要调优
索引是把双刃剑:写成本需考量
简单即有效:复杂方案常是错的开始
记住:SQL 优化是 80% 的常识 + 20% 的深度