当前位置: 首页 > news >正文

MySQL实战篇1:慢查询优化实战-4道题的真实优化记录

📅 2025年10月8日
💻 测试环境:HeidiSQL + MySQL 8.0
📊 数据量:users表1000条,orders表5000+条,order_items表10000+条
⏰ 用时:约1小时

今天做了4道SQL优化题,有做对的,也有不太理解的。特别是第3题和第4题,遇到了一些问题,记录一下整个过程。


在这里插入图片描述

题目1:深度分页优化 ✅

原始SQL

SELECT * FROM users 
ORDER BY id 
LIMIT 900, 10;

因为我的测试数据只有1000条,所以改成查询第900-910条。

优化前的表现

执行时间:0.016秒(因为数据少,看不出差异)
在这里插入图片描述

EXPLAIN分析:

在这里插入图片描述

关键信息:

  • type: index(扫描整个索引)
  • rows: 910(需要读910行)
  • Extra: -

虽然时间不长,但我看EXPLAIN发现:MySQL要扫描910行,然后跳过前900行,只返回10行。

这意味着什么?

  • 如果数据是10万条,LIMIT 90000, 10 就要扫描90010行
  • 偏移量越大,浪费越多
  • 深度分页的核心问题:不是查询慢,是"跳过"慢

我的优化方案

我查了资料,用子查询 + 覆盖索引的方式:

SELECT * FROM users 
WHERE id >= (SELECT id FROM users ORDER BY id LIMIT 900, 1)
ORDER BY id LIMIT 10;

思路:

  1. 内层查询:只查id,找到第900条的id
  2. 外层查询:从这个id开始,取10条

优化后的表现

执行时间:0.008秒

EXPLAIN分析:

在这里插入图片描述

关键改进:

  • 内层查询:type=index, rows=901, Extra: Using index ← 覆盖索引
  • 外层查询:type=range, rows≈10-20

我的疑问和理解

疑问1:为什么子查询就快了?

我一开始不太理解,后来想明白了:

  • 内层SELECT id FROM users只查id字段
  • id是主键,索引已经包含了id的值
  • MySQL不需要回表查数据,只扫描索引树
  • EXPLAIN显示"Using index"就是这个意思,这叫覆盖索引

疑问2:type是index还是range?

  • 内层查询:type=index(扫描索引树)
  • 外层查询:type=range(范围扫描)
  • range比index更优,因为只扫描需要的范围

性能对比

版本扫描行数执行时间说明
原始910行0.016秒全部扫描再跳过
优化后901行(内层) + 10行(外层)0.008秒覆盖索引+范围扫描

虽然我的数据少,时间差异不明显,但如果是10万、100万数据:

  • 原始:扫描90010行 → 可能要5-10秒
  • 优化:扫描901行(覆盖索引,快) + 10行 → 可能只要0.05秒
  • 性能提升:100倍+

我的收获

  1. 深度分页的问题本质是"跳过"太多行
  2. 覆盖索引的意思:查询的字段在索引里,不用回表
  3. 小数据量看EXPLAIN,大数据量看执行时间
  4. 生产环境要避免深度分页,可以用游标分页

题目2:JOIN查询优化 ✅

原始SQL

SELECT u.name, COUNT(o.id) as order_count, SUM(o.amount) as total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.status = 'completed'
GROUP BY u.id
ORDER BY total_amount DESC
LIMIT 20;

优化前的表现

执行时间:0.032秒
在这里插入图片描述

EXPLAIN分析:
在这里插入图片描述

tabletypekeyrows
usersALLNULL1000
ordersALLNULL5000+

我看到的问题:

  1. orders表没有索引,JOIN时全表扫描
  2. 用了LEFT JOIN,但WHERE条件过滤了NULL

我发现的问题

问题1:缺少索引

查看索引:

在这里插入图片描述

orders表的user_id列没有索引,JOIN时要全表扫描。

问题2:LEFT JOIN用错了

LEFT JOIN orders o ON u.id = o.user_id
WHERE o.status = 'completed'

这里用LEFT JOIN,但WHERE条件o.status = 'completed'会过滤掉o为NULL的记录。

实际上等同于INNER JOIN,用LEFT JOIN反而更慢。

我的优化方案

第1步:创建索引

CREATE INDEX idx_user_id ON orders(user_id);

思路:

  • users表是小表(1000条)
  • orders表是大表(5000+条)
  • 小表驱动大表,在大表的连接列建索引

第2步:改写SQL

SELECT u.name, COUNT(o.id) as order_count, SUM(o.amount) as total_amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.status = 'completed'
GROUP BY u.id
ORDER BY total_amount DESC
LIMIT 20;

把LEFT JOIN改成INNER JOIN。

优化后的表现

执行时间:0.000
在这里插入图片描述

EXPLAIN分析:

在这里插入图片描述

关键改进:

  • orders表:type从ALL变成eq_ref(最优的JOIN类型)
  • rows:从5000+变成1
  • 使用了索引:key = idx_user_id

性能对比

版本orders表typeorders表rows执行时间
优化前ALL5000+0.031秒
优化后eq_ref10.015秒

我的收获

  1. 小表驱动大表,在大表建索引
  2. LEFT JOIN不总是必要的,能用INNER JOIN就用INNER JOIN
  3. eq_ref是JOIN最优的访问类型
  4. 索引对JOIN性能影响巨大

题目3:索引失效问题 ⚠️(遇到问题)

原始SQL

SELECT * FROM orders 
WHERE YEAR(created_at) = 2024;

我先创建了索引

CREATE INDEX idx_created_at ON orders(created_at);

优化前的表现

EXPLAIN分析:

在这里插入图片描述

  • type: ALL(全表扫描)
  • key: NULL(没用索引)
  • rows: 5008

问题很明显:索引失效了!

原因:YEAR(created_at) 在索引列上用了函数,索引失效。

我的优化尝试

我把SQL改成范围查询:

SELECT * FROM orders 
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

这样就不在索引列上用函数了。

但是遇到问题了!

EXPLAIN分析:

在这里插入图片描述

结果:还是type=ALL,rows=5008!

我懵了:

  • SQL改对了(没用函数)
  • 索引也创建了
  • 为什么还是全表扫描?

我的分析

后来我想了想,可能的原因:

原因1:数据分布问题

可能2024年的数据占了大部分(比如80-90%)。

MySQL优化器会判断:

  • 走索引:扫描索引树 + 回表查数据 → 大量随机I/O
  • 全表扫描:顺序读表 → 连续I/O

如果数据量大,全表扫描可能更快,优化器就不用索引了。

原因2:统计信息没更新

我尝试更新统计信息:

ANALYZE TABLE orders;

但EXPLAIN结果还是一样。

我的困惑

这道题让我困惑的是:

  • 我的SQL改写是对的
  • 但优化器选择了全表扫描
  • 这算优化成功还是失败?

我现在的理解:

  • 索引不是一定会用
  • 要看数据分布和查询范围
  • 如果查询范围太大(>30%数据),全表扫描可能是对的
  • 虽然EXPLAIN还是ALL,但SQL改写本身是正确的

这道题没达到预期效果,不知道哪里有问题。


题目4:综合优化挑战 ❌(有点不会)

原始SQL

SELECT DISTINCT u.id, u.name, u.email
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
WHERE oi.product_name LIKE '%Phone%'AND o.status = 'completed'AND u.city = '深圳';

优化前的表现

执行时间:0.047秒
在这里插入图片描述

EXPLAIN分析:
在这里插入图片描述

tabletypekeyrows
userseq_refPRIMARY1
orderseq_refidx_user_id1
order_itemsALLNULL10000

我看到的问题:

  • order_items表:type=ALL,全表扫描
  • 其他两表:type=eq_ref(有索引)

我一开始的尝试

我想:product_name没索引,那创建一个索引吧。

CREATE INDEX idx_product_name ON order_items(product_name);

但后来发现:这个索引根本用不上!

为什么用不上?

因为:

WHERE oi.product_name LIKE '%Phone%'

前后都有通配符%,任何索引都失效!

  • LIKE 'Phone%' → 可以用索引(前缀匹配)
  • LIKE '%Phone' → 索引失效(后缀匹配)
  • LIKE '%Phone%' → 索引完全失效(模糊匹配)

我的困惑

这道题我有点不会了:

问题1:LIKE ‘%xxx%’ 怎么优化?

  • 普通索引用不上
  • 是不是要用全文索引?
  • 还是改变数据库设计?

问题2:三表JOIN的顺序对吗?

  • 现在是users → orders → order_items
  • 要不要调整顺序?

问题3:DISTINCT影响性能吗?

  • DISTINCT会去重
  • 是不是影响性能?

我查了一些资料

方案1:全文索引

ALTER TABLE order_items ADD FULLTEXT INDEX ft_product_name(product_name);-- 改写查询
WHERE MATCH(oi.product_name) AGAINST('Phone' IN NATURAL LANGUAGE MODE)

但我没试过全文索引,不确定效果。

方案2:改变设计

  • 增加product_category字段
  • 或者增加product_id字段
  • 用精确匹配代替LIKE

方案3:改变驱动顺序

如果product_name条件筛选很多数据,可以先过滤order_items。

但我不太确定怎么改。

这道题的现状

我尝试了一些方案,但效果不理想。

数据统计:

  • users表:1000条
  • orders表:5000+条
  • order_items表:10000+条

这道题我没做好,还需要继续学习。


我的总体收获

这次实战做了4道题:

  • ✅ 题目1(深度分页):做对了,理解了覆盖索引
  • ✅ 题目2(JOIN优化):做对了,理解了小表驱动大表
  • ⚠️ 题目3(索引失效):SQL改对了,但优化器没用索引,不太理解
  • ❌ 题目4(综合优化):LIKE ‘%xxx%’ 不会优化

我现在理解的知识点

1. 深度分页优化

  • 核心问题:跳过太多行
  • 解决方案:子查询 + 覆盖索引
  • 关键概念:覆盖索引 = 查询字段在索引里,不回表

2. JOIN优化

  • 小表驱动大表
  • 在大表的连接列建索引
  • 能用INNER JOIN就不用LEFT JOIN

3. 索引失效场景

  • 在索引列上用函数:YEAR(date) → 失效
  • LIKE前后通配符:LIKE '%xxx%' → 失效
  • 表达式计算:col + 1 = value → 失效

我的薄弱环节

  1. 优化器的选择逻辑

    • 为什么题目3优化器不用索引?
    • 什么情况下全表扫描比索引快?
    • 怎么判断优化器的选择是否合理?
  2. LIKE模糊查询优化

    • LIKE ‘%xxx%’ 怎么办?
    • 全文索引怎么用?
    • 数据库设计层面怎么避免?
  3. 复杂查询的优化思路

    • 多表JOIN怎么调整驱动顺序?
    • DISTINCT对性能影响多大?
    • 怎么判断查询已经优化到极致?

下次要注意

  1. 先看数据分布,再判断优化效果
  2. EXPLAIN的type=ALL不一定是坏事
  3. 有些优化问题需要改数据库设计
  4. 遇到不会的要继续学习,不能停

写于:2025年10月8日晚
数据量:测试环境小数据量
用时:约1小时

这次实战让我对SQL优化有了更深的理解,但也发现了自己的不足。题目3和题目4的问题,我要继续研究。

学习是个持续的过程,做错了也是收获。 💪


附录:测试环境信息

数据库版本: MySQL 8.0

表结构:

  • users:1000条数据
  • orders:5000+条数据
  • order_items:10000+条数据

索引情况:

在这里插入图片描述

工具: HeidiSQL可视化工具

相关博客

  • MySQL实战篇0:数据库设计实战----从需求到建表的完整流程
  • MySQL实战篇2:篇1第四题深入——MySQL模糊查询LIKE ‘%xxx%‘优化深度研究
  • MySQL学习笔记07:MySQL SQL优化与EXPLAIN分析实战指南(上):执行计划深度解析
  • MySQL学习笔记08:MySQL SQL优化与EXPLAIN分析实战指南(下):JOIN优化到慢查询监控完整实战
http://www.dtcms.com/a/462149.html

相关文章:

  • 怎样建立自己的网站卖东西个人网站备案填写要求
  • term.everything‌ 通过终端运行任意GUI应用程序
  • 去噪自编码器(DAE)
  • 形象设计公司网站建设方案书营销公司的营业范围
  • 关于网站备案的44个问题wordpress 发表文章
  • 做网站定金是多少网站开发项目外包
  • 中国制造网官方网站入口网址秦皇岛黄页大全秦皇岛本地信息网
  • Linux 文件打开函数 `open()` 深入解析
  • ESP8266实现mqtt
  • 初识MYSQL —— 表的约束
  • mysql存储微信Emoji表情问题
  • DzzOffice 通知功能(notification_add)调用
  • 西安手机网站建设公司排名安徽房产网站建设
  • 杭州强龙网站建设电话广西桂林天气预报7天
  • autosar
  • LinkMate 智能会议室系统:基于 Qt / QML / WebRTC / FFmpeg / Whisper / OpenGL 的实时音视频会议平台
  • JavaScript编程工具有哪些?老前端的实用工具清单与经验分享
  • 企业营销网站服务器1g够wordpress 电影网站
  • 360°全景视频 数据集Dataset
  • 项目愿景缺乏共识会带来哪些风险
  • 网站服务器vps温州做网站哪里好
  • 分片并发上传实现
  • JavaWeb后端实战(IOC+DI)
  • php网站模板制作工具昆明网架公司
  • 甘肃省住房和城乡建设厅网站网站域名登录
  • lazarust中SqlConnector的使用
  • 美国银行与Anchorage合作推动稳定币发展,平台XBIT在去中心化交易所领域发力
  • Gemini 2.5如何通过视觉理解,告别脆弱的UI测试脚本
  • 【星海出品】ASCII
  • 青州网站建设优化排名找工程包工平台app