MySQL慢查询优化案例:真实案例+EXPLAIN分析——性能提升10倍!

一、问题场景

业务背景:订单列表查询,显示用户的所有订单

sql 复制代码
-- 查询用户订单列表
SELECT * FROM orders
WHERE user_id = 12345
  AND status IN (1, 2, 3)
  AND create_time >= '2024-01-01'
ORDER BY create_time DESC
LIMIT 10;

问题表现

  • 执行时间:5.2秒
  • 返回数据:10条
  • 表数据量:500万条

二、问题定位

2.1 使用EXPLAIN分析

sql 复制代码
EXPLAIN SELECT * FROM orders
WHERE user_id = 12345
  AND status IN (1, 2, 3)
  AND create_time >= '2024-01-01'
ORDER BY create_time DESC
LIMIT 10;

结果

sql 复制代码
+----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows    | Extra       |
+----+-------------+--------+------+---------------+------+---------+------+---------+-------------+
|  1 | SIMPLE      | orders | ALL  | NULL          | NULL | NULL    | NULL | 5000000 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+---------+-------------+

关键信息

  • type = ALL:全表扫描(最差)
  • key = NULL:没有使用索引
  • rows = 5000000:扫描了500万行!

2.2 查看当前索引

sql 复制代码
SHOW INDEX FROM orders;

结果

sql 复制代码
+-------+------------+----------+--------------+-------------+-----------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation |
+-------+------------+----------+--------------+-------------+-----------+
| orders|          1 | PRIMARY  |            1 | id          | A         |
+-------+------------+----------+--------------+-------------+-----------+

发现问题:只有主键索引,没有其他索引!

三、优化方案

3.1 添加复合索引

lua 复制代码
-- 添加复合索引
CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);

为什么是这个顺序?

ini 复制代码
索引 idx_user_status_time(user_id, status, create_time)

查询条件:
WHERE user_id = 12345          ✓ 最左前缀匹配
  AND status IN (1, 2, 3)      ✓ 可以匹配
  AND create_time >= '2024-01-01'  ✓ 可以匹配
ORDER BY create_time DESC      ✓ 利用索引排序

能使用的查询:
✅ WHERE user_id = 12345
✅ WHERE user_id = 12345 AND status = 1
✅ WHERE user_id = 12345 AND status IN (1, 2, 3)
✅ WHERE user_id = 12345 AND create_time >= '2024-01-01'

不能使用的查询:
✗ WHERE status = 1  (跳过了user_id)
✗ WHERE create_time >= '2024-01-01'  (跳过了user_id和status)

3.2 再次EXPLAIN

sql 复制代码
EXPLAIN SELECT * FROM orders
WHERE user_id = 12345
  AND status IN (1, 2, 3)
  AND create_time >= '2024-01-01'
ORDER BY create_time DESC
LIMIT 10;

结果

sql 复制代码
+----+-------------+--------+-------+----------------------+--------------------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys        | key                | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+----------------------+--------------------+---------+------+------+-------------+
|  1 | SIMPLE      | orders | range | idx_user_status_time | idx_user_status_time | 17      | NULL | 1500 | Using where |
+----+-------------+--------+-------+----------------------+--------------------+---------+------+------+-------------+

优化效果

  • type = range:范围查询(比ALL好很多)
  • key = idx_user_status_time:使用了索引
  • rows = 1500:扫描了1500行(比500万行少很多)
  • 执行时间:从5.2秒降到0.03秒,提升173倍!

四、进一步优化

4.1 问题:SELECT * 查询所有字段

sql 复制代码
-- ❌ 不好:查询所有字段
SELECT * FROM orders WHERE user_id = 12345;

-- ✅ 好:只查询需要的字段
SELECT id, order_no, amount, status, create_time
FROM orders WHERE user_id = 12345;

4.2 问题:LIMIT深度分页

sql 复制代码
-- 问题:LIMIT 1000000, 10 需要扫描100万行
SELECT * FROM orders
WHERE user_id = 12345
ORDER BY create_time DESC
LIMIT 1000000, 10;

-- 解决方案1:使用子查询
SELECT * FROM orders
WHERE id >= (
    SELECT id FROM orders
    WHERE user_id = 12345
    ORDER BY create_time DESC
    LIMIT 1000000, 1
)
ORDER BY create_time DESC
LIMIT 10;

-- 解决方案2:记录上次的最大ID
SELECT * FROM orders
WHERE user_id = 12345 AND id > 1234567
ORDER BY create_time DESC
LIMIT 10;

4.3 问题:索引覆盖

sql 复制代码
-- 如果查询的字段都在索引中,就不需要回表查询

-- 当前索引:idx_user_status_time(user_id, status, create_time)
-- 查询字段:id, order_no, amount, status, create_time

-- ❌ 不好的查询:需要回表
SELECT id, order_no, amount, status, create_time
FROM orders WHERE user_id = 12345;

-- ✅ 优化:添加覆盖索引
CREATE INDEX idx_user_status_time_cover ON orders(user_id, status, create_time, id, order_no, amount);

-- 再次EXPLAIN:
-- Extra: Using index(不需要回表,直接从索引获取)

五、真实案例优化总结

优化项 优化前 优化后 提升
执行时间 5.2秒 0.03秒 173倍
扫描行数 500万行 1500行 3333倍
是否使用索引 -
type类型 ALL range -

六、EXPLAIN字段详解

字段 说明 好的值 差的值
id 查询序列号 - -
select_type 查询类型 SIMPLE SUBQUERY、UNION
type 访问类型 const、eq_ref、ref、range ALL
key 实际使用的索引 有索引 NULL
key_len 索引长度 - -
rows 扫描行数
Extra 额外信息 Using index Using filesort、Using temporary

type类型优劣排序

sql 复制代码
const > eq_ref > ref > range > index > ALL
 最好                                       最差

七、常见慢查询场景及优化

7.1 场景1:使用了函数

sql 复制代码
-- ❌ 索引失效
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';

-- ✅ 使用索引
SELECT * FROM orders
WHERE create_time >= '2024-01-01 00:00:00'
  AND create_time <= '2024-01-01 23:59:59';

7.2 场景2:LIKE模糊查询

sql 复制代码
-- ❌ 索引失效
SELECT * FROM orders WHERE order_no LIKE '%2024%';

-- ✅ 使用索引
SELECT * FROM orders WHERE order_no LIKE '2024%';

7.3 场景3:OR条件

sql 复制代码
-- ❌ 索引失效
SELECT * FROM orders WHERE user_id = 1 OR status = 1;

-- ✅ 使用UNION ALL
SELECT * FROM orders WHERE user_id = 1
UNION ALL
SELECT * FROM orders WHERE status = 1;

7.4 场景4:NOT、!=、<>

sql 复制代码
-- ❌ 索引失效
SELECT * FROM orders WHERE status != 1;

-- ✅ 使用索引
SELECT * FROM orders WHERE status > 1;

八、慢查询监控

8.1 开启慢查询日志

sql 复制代码
-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;  -- 超过2秒的查询记录

-- 查看慢查询日志位置
SHOW VARIABLES LIKE 'slow_query_log_file';

8.2 分析慢查询

bash 复制代码
# 查看慢查询日志
tail -f /var/log/mysql/slow.log

# 使用mysqldumpslow工具分析
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# -s t: 按时间排序
# -t 10: 显示前10条

九、总结

今天我们学到了:

要点 说明
问题定位 EXPLAIN分析type、key、rows
核心优化 添加合适的索引(复合索引)
索引顺序 遵循最左前缀原则
覆盖索引 索引包含查询字段,避免回表
常见坑 函数、LIKE、OR、NOT

今日互动

你的项目有过慢查询吗?是用什么方法优化的?

相关推荐
好家伙VCC16 分钟前
区块链双向支付通道实战:从签名到结算
java·后端·区块链·asp.net
我登哥MVP1 小时前
Spring Boot 从“会用”到“精通”:参数解析原理
java·spring boot·后端·spring·servlet·maven·intellij-idea
JustHappy2 小时前
古法编程秘籍(五):什么是进程和线程?从软件到 CPU 的一次完整旅程
前端·后端·代码规范
BLSxiaopanlaile2 小时前
关于常见 map的一些比较探究
后端
花大师2 小时前
基于深度学习的鼠标轨迹真实性检测系统
后端
小江的记录本2 小时前
【Spring全家桶】Spring Cloud 2023.0.x:微服务核心理论、CAP/BASE定理(附《思维导图》+《面试高频考点清单》)
java·spring boot·后端·spring·spring cloud·微服务·面试
我登哥MVP3 小时前
Spring Boot 从“会用”到“精通”:Model-Map原理
java·spring boot·后端·spring·servlet·maven·mybatis
㳺三才人子3 小时前
初探 Flask-WTF
后端·python·flask·html5