MySQL慢查询优化从30秒到300毫秒的完整过程
作者:嘻哈baby
文章介绍了如何优化一个响应时间为30秒的SQL接口,将响应时间优化到300毫秒的过程,通过开启慢查询日志、分析SQL执行计划、添加联合索引和优化查询技巧等步骤,解决了慢查询问题,文章还分享了索引设计原则、实用工具和远程数据库排查技巧,需要的朋友可以参考下
最近接手一个老项目,某个列表接口响应时间30秒,用户疯狂投诉。
排查下来是SQL问题,优化后降到300毫秒。记录一下完整过程。
一、发现问题
1.1 开启慢查询日志
-- 查看是否开启 SHOW VARIABLES LIKE 'slow_query%'; SHOW VARIABLES LIKE 'long_query_time'; -- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; -- 超过1秒记录 SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
1.2 分析慢查询日志
# 用mysqldumpslow分析 mysqldumpslow -s t -t 10 /var/log/mysql/slow.log # 输出 Count: 1532 Time=28.35s (43424s) Lock=0.00s (0s) Rows=100.0 (153200) SELECT * FROM orders WHERE user_id = N AND status = N ORDER BY create_time DESC LIMIT N, N
找到了!这条SQL执行了1532次,平均28秒。
二、分析SQL
2.1 问题SQL
SELECT * FROM orders WHERE user_id = 12345 AND status = 1 ORDER BY create_time DESC LIMIT 0, 20;
看起来很简单,为什么慢?
2.2 EXPLAIN分析
EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = 1 ORDER BY create_time DESC LIMIT 0, 20;
+----+-------------+--------+------+---------------+------+---------+------+----------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | rows | filtered | Extra | +----+-------------+--------+------+---------------+------+---------+------+----------+-----------------------------+ | 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | 5000000 | 0.10 | Using where; Using filesort | +----+-------------+--------+------+---------------+------+---------+------+----------+-----------------------------+
问题暴露了:
type = ALL:全表扫描key = NULL:没用到索引rows = 5000000:扫描500万行Using filesort:额外排序
2.3 查看表结构
SHOW CREATE TABLE orders;
CREATE TABLE `orders` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `status` tinyint NOT NULL DEFAULT '0', `amount` decimal(10,2) NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
果然,只有主键索引,没有业务索引。
三、优化方案
3.1 添加联合索引
-- 创建联合索引 ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);
再次EXPLAIN:
+----+-------------+--------+------+---------------------+---------------------+---------+-------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------------+---------------------+---------+-------------+------+-------+ | 1 | SIMPLE | orders | ref | idx_user_status_time| idx_user_status_time| 9 | const,const | 156 | NULL | +----+-------------+--------+------+---------------------+---------------------+---------+-------------+------+-------+
完美:
type = ref:使用索引rows = 156:只扫描156行Extra = NULL:不需要额外排序
3.2 为什么这样设计索引?
联合索引顺序:(user_id, status, create_time) 查询条件:WHERE user_id = ? AND status = ? 排序条件:ORDER BY create_time 索引匹配过程: 1. user_id = 12345 → 定位到用户的订单 2. status = 1 → 进一步过滤状态 3. create_time → 索引本身有序,无需filesort
联合索引设计原则:
- 等值查询的列放前面
- 排序的列放最后
- 遵循最左前缀原则
3.3 优化效果
优化前:28.35秒,扫描500万行 优化后:0.003秒,扫描156行 提升:9000倍+
四、更多优化技巧
4.1 避免SELECT *
-- 差:查询所有字段 SELECT * FROM orders WHERE ... -- 好:只查需要的字段 SELECT id, user_id, amount, create_time FROM orders WHERE ...
好处:
- 减少网络传输
- 可能用到覆盖索引
4.2 覆盖索引
如果查询的字段都在索引里,不需要回表:
-- 索引:idx_user_status_time (user_id, status, create_time) -- 这个查询可以用覆盖索引 SELECT user_id, status, create_time FROM orders WHERE user_id = 12345; -- EXPLAIN显示 Using index
4.3 避免索引失效
-- ❌ 对索引列使用函数 SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01'; -- ✅ 改写 SELECT * FROM orders WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02'; -- ❌ 隐式类型转换 SELECT * FROM orders WHERE user_id = '12345'; -- user_id是bigint -- ✅ 类型一致 SELECT * FROM orders WHERE user_id = 12345; -- ❌ LIKE前置通配符 SELECT * FROM orders WHERE order_no LIKE '%ABC'; -- ✅ LIKE后置通配符(可以用索引) SELECT * FROM orders WHERE order_no LIKE 'ABC%';
4.4 分页优化
-- ❌ 深分页很慢 SELECT * FROM orders ORDER BY id LIMIT 1000000, 20; -- 需要扫描100万行 -- ✅ 用游标分页 SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20; -- 直接定位,很快
五、索引设计原则
5.1 什么时候建索引?
| 场景 | 是否建索引 |
|---|---|
| WHERE条件频繁查询的列 | ✅ 是 |
| ORDER BY排序的列 | ✅ 是 |
| JOIN关联的列 | ✅ 是 |
| 区分度低的列(如性别) | ❌ 否 |
| 频繁更新的列 | ⚠️ 权衡 |
5.2 联合索引顺序
原则: 1. 区分度高的列放前面 2. 等值查询的列放前面 3. 排序列放最后
六、实用工具
6.1 慢查询分析
# mysqldumpslow mysqldumpslow -s t -t 10 slow.log # 按时间排序,前10条 # pt-query-digest(推荐) pt-query-digest slow.log > report.txt
6.2 EXPLAIN详解
type(重要,从好到差): - system/const:常量查询 - eq_ref:主键/唯一索引 - ref:普通索引 - range:范围扫描 - index:索引全扫描 - ALL:全表扫描 ❌ Extra(重要): - Using index:覆盖索引 ✅ - Using where:需要回表过滤 - Using filesort:额外排序 ⚠️ - Using temporary:临时表 ⚠️
七、远程数据库排查技巧
有时候问题数据库在测试环境,本地连不上怎么办?
我的做法是用组网工具把本地和测试服务器连起来。之前用VPN,经常断还慢。现在用星空组网,本地直接连测试环境的MySQL:
# 组网后直接用虚拟IP连接 mysql -h 192.168.188.10 -u root -p
EXPLAIN、慢查询分析都能直接在本地跑,比登服务器方便多了。
总结
SQL优化核心步骤:
1. 开启慢查询日志 → 发现问题SQL 2. EXPLAIN分析 → 定位问题原因 3. 添加/优化索引 → 解决问题 4. 再次EXPLAIN → 验证效果 5. 线上执行 → 监控观察
记住几个原则:
- 避免全表扫描
- 利用覆盖索引
- 注意索引失效场景
- 联合索引最左前缀
以上就是MySQL慢查询优化从30秒到300毫秒的完整过程的详细内容,更多关于MySQL慢查询优化30秒到300毫秒的资料请关注脚本之家其它相关文章!
