Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL慢查询优化30秒到300毫秒

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 |
+----+-------------+--------+------+---------------+------+---------+------+----------+-----------------------------+

问题暴露了:

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  |
+----+-------------+--------+------+---------------------+---------------------+---------+-------------+------+-------+

完美:

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

联合索引设计原则:

  1. 等值查询的列放前面
  2. 排序的列放最后
  3. 遵循最左前缀原则

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毫秒的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文