目录
慢SQL日志分析与诊断
开启慢查询日志
慢查询日志分析工具
慢SQL优化策略
1. 避免SELECT * 查询
2. 创建高效索引
索引选择原则
索引使用注意事项
3. 使用EXPLAIN分析执行计划
4. 优化排序操作
5. 解决深分页问题
6. 避免全表扫描
7. 优化JOIN操作
8. 合理使用子查询
高级优化技巧
1. 使用覆盖索引
2. 索引条件下推(ICP)
3. 使用批处理减少交互
4. 合理使用临时表
名词解释
总结
慢SQL日志分析与诊断
开启慢查询日志
-- 开启慢查询日志功能
SET GLOBAL slow_query_log = 'ON';-- 设置慢SQL时间阈值(单位:秒),超过此时间的查询会被记录
SET GLOBAL long_query_time = 5;-- 查看慢查询日志文件位置
SHOW GLOBAL VARIABLES LIKE 'slow_query_log_file';-- 使用mysqldumpslow工具分析慢查询日志
-- 常用参数:-s排序方式(t按时间,c按次数),-t显示前N条
-- 示例:mysqldumpslow -s t -t 10 /var/logs/mysql/slow.log
慢查询日志分析工具
除了mysqldumpslow
,还可以使用:
-
pt-query-digest(Percona Toolkit的一部分):更强大的日志分析工具
pt-query-digest /var/logs/mysql/slow.log
-
MySQL Workbench:图形化界面分析工具
慢SQL优化策略
1. 避免SELECT * 查询
-- 不推荐
SELECT * FROM users WHERE id = 1;-- 推荐:只查询需要的列
SELECT id, username, email FROM users WHERE id = 1;
原因:
- 增加网络I/O负担
- 当表中有大字段(如TEXT/BLOB)时性能影响显著
- 可能使覆盖索引失效(后面会解释)
2. 创建高效索引
索引选择原则
- 高区分度:选择区分度高的列建索引(如用户ID比性别更适合)
- 常用查询条件:为WHERE、JOIN、ORDER BY、GROUP BY中的列建索引
- 短字段优先:整型字段比字符串字段更适合做索引
-- 创建单列索引
CREATE INDEX idx_username ON users(username);-- 创建复合索引
CREATE INDEX idx_status_created ON orders(status, created_at);
索引使用注意事项
-
最左匹配原则:复合索引必须从左到右使用
-- 对于INDEX(a, b, c) WHERE a = 1 AND b = 2 -- 使用索引 WHERE b = 2 AND c = 3 -- 不使用索引(缺少a) WHERE a = 1 AND c = 3 -- 部分使用索引(只用a)
-
避免索引失效:
- 不要在索引列上使用函数:
WHERE YEAR(create_time) = 2023
- 避免隐式类型转换:
WHERE user_id = '123'
(user_id是整型) - 避免使用
!=
、NOT IN
、IS NULL
等操作符
- 不要在索引列上使用函数:
3. 使用EXPLAIN分析执行计划
EXPLAIN SELECT * FROM users WHERE username = 'john';
关键指标解读:
- type:访问类型(从好到差:system > const > eq_ref > ref > range > index > ALL)
- possible_keys:可能使用的索引
- key:实际使用的索引
- rows:预估需要检查的行数
- Extra:额外信息(如Using filesort、Using temporary表示性能问题)
4. 优化排序操作
-- 不推荐:大数据集文件排序
SELECT * FROM products ORDER BY price DESC LIMIT 100;-- 推荐:为排序字段添加索引
ALTER TABLE products ADD INDEX idx_price (price);
SELECT * FROM products ORDER BY price DESC LIMIT 100;
原理:B+树索引本身是有序的,利用索引可以避免内存排序(Using filesort)
5. 解决深分页问题
问题:LIMIT 10000, 20
会先读取10020条记录,然后丢弃前10000条
优化方案:
-
使用覆盖索引+延迟关联
SELECT * FROM products JOIN (SELECT id FROM products WHERE category_id = 5 ORDER BY created_at DESC LIMIT 10000, 20 ) AS tmp USING(id);
-
记录上次查询位置(适用于有序数据)
-- 第一页 SELECT * FROM products ORDER BY id DESC LIMIT 20;-- 后续页(假设上一页最后一条记录的id是12345) SELECT * FROM products WHERE id < 12345 ORDER BY id DESC LIMIT 20;
6. 避免全表扫描
- 为查询条件添加适当的索引
- 避免在WHERE子句中对字段进行运算或使用函数
- 使用合适的查询条件,避免过于宽泛的条件
7. 优化JOIN操作
- 确保JOIN字段有索引
- 小表驱动大表(MySQL优化器通常会自动处理)
- 避免多表JOIN(超过3个表考虑反范式化设计)
8. 合理使用子查询
-- 不推荐:相关子查询
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000
);-- 推荐:改用JOIN
SELECT DISTINCT u.* FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.amount > 1000;
高级优化技巧
1. 使用覆盖索引
覆盖索引:查询的所有字段都包含在索引中,无需回表
-- 假设有INDEX(username, email)
SELECT username, email FROM users WHERE username = 'john';
优势:减少I/O操作,提高查询速度
2. 索引条件下推(ICP)
MySQL 5.6+特性,将WHERE条件推到存储引擎层过滤
-- 假设有INDEX(a, b)
SELECT * FROM table WHERE a = 1 AND b LIKE '%test%';
3. 使用批处理减少交互
-- 不推荐
INSERT INTO users(name) VALUES ('a');
INSERT INTO users(name) VALUES ('b');-- 推荐
INSERT INTO users(name) VALUES ('a'), ('b');
4. 合理使用临时表
对于复杂查询,可以考虑使用临时表分步处理
CREATE TEMPORARY TABLE temp_orders
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
HAVING total > 1000;SELECT u.* FROM users u
JOIN temp_orders t ON u.id = t.user_id;
名词解释
-
B+树:MySQL索引的数据结构,特点是:
- 所有数据都存储在叶子节点
- 叶子节点通过指针连接,适合范围查询
- 树的高度低,查询效率稳定
-
回表:当使用非主键索引查询时,先通过索引找到主键,再通过主键索引查找完整数据的过程
-
覆盖索引:查询的列都包含在索引中,无需回表
-
最左匹配原则:复合索引必须从左到右使用,不能跳过前面的列
-
深分页:当LIMIT offset很大时(如LIMIT 100000, 10),MySQL需要先读取offset+limit条记录,性能差
-
Using filesort:表示MySQL需要进行额外的排序操作,通常是因为没有使用索引排序
-
Using temporary:表示MySQL需要创建临时表来处理查询,常见于GROUP BY、ORDER BY等操作
总结
慢SQL优化是一个系统工程,需要:
- 通过慢查询日志定位问题SQL
- 使用EXPLAIN分析执行计划
- 针对性应用优化策略
- 持续监控优化效果
记住:索引不是越多越好,每个索引都会增加写操作的成本。好的数据库设计加上合理的索引策略,才能获得最佳性能。