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

今日互动

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

相关推荐
还没学会摸鱼的钓鱼仔1 小时前
手撕 LangChain Deep Agents 源码 (一):create_deep_agent 是如何"组装"出一个 AI 操作系统的
后端
用户298698530141 小时前
Java 操作 Word 文档:数学公式与符号的插入方法
java·后端
小撒的私房菜1 小时前
Day 5:Agent Loop——整个系列里最关键的一天
人工智能·后端
XovH2 小时前
Django 模型(Model)设计:无需 SQL,用 Python 类定义你的数据库
后端
传说之后2 小时前
Go 调用 OpenAI 兼容 API:对话、流式输出、上下文与图片识别
后端
传说之后2 小时前
Go Channel 解析:原理与实践
后端
XovH2 小时前
Django Admin:5 分钟搭建一个全功能的后台管理系统
后端
阿星做前端2 小时前
不想再给ai回复下一步了,于是我给agent装上了一个自动挡
前端·后端·程序员