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

MySQL `SELECT` 查询优化:原理 + 案例 + 实战总结


一、访问方法优化(Access Methods)

✅ 核心思想:

选择最高效的路径读取数据:能用索引就不用全表扫描,能跳过就别遍历。


🔹 案例1:索引查找 vs 全表扫描

-- 表结构
CREATE TABLE users (id INT PRIMARY KEY,name VARCHAR(50),age INT,city VARCHAR(30),INDEX idx_age (age)
);-- 查询:找出所有30岁的人
SELECT * FROM users WHERE age = 30;
❓ 执行方式?
  • idx_age 索引 → 使用 Index Range Scan,只扫描 age=30 的部分。
  • 无索引 → 全表扫描(type: ALL),性能差。

结论:为常用于查询条件的列建立索引。


🔹 案例2:Skip Scan(MySQL 8.0+ 新特性)

-- 复合索引
CREATE INDEX idx_name_age ON users(name, age);-- 查询:找年龄为25的所有人(不指定name)
SELECT * FROM users WHERE age = 25;
❓ 能用索引吗?
  • 在 MySQL < 8.0.16:❌ 不能,因为跳过了前导列 name
  • 在 MySQL ≥ 8.0.16:✅ 可以!优化器使用 Skip Scan
    • 遍历不同的 name 值;
    • 对每个 name 查找 age=25 的记录。

结论:升级到 MySQL 8.0+ 后,某些“看似无法用索引”的查询也能被优化。


二、JOIN 连接优化

✅ 核心思想:

小结果集驱动大表,优先使用索引连接(Index Nested Loop)


🔹 案例3:高效 JOIN 顺序

-- 表结构
CREATE TABLE orders (order_id INT PRIMARY KEY,user_id INT,amount DECIMAL(10,2),INDEX idx_user_id (user_id)
);
CREATE TABLE users (id INT PRIMARY KEY,name VARCHAR(50)
);-- 查询:获取每个用户的订单总额
SELECT u.name, SUM(o.amount)
FROM users u
JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;
❓ 执行计划?
  1. 先扫描 users 表(驱动表);
  2. 对每条用户记录,在 orders 表中通过 idx_user_id 快速查找其所有订单(Index Lookup);
  3. 聚合计算。

优点:避免了全表扫描 orders,减少了 I/O。

⚠️ 如果反过来(先扫 orders),就需要临时表或排序来去重用户,效率更低。


🔹 案例4:常量表优化

SELECT *
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.id = 100;
❓ 优化器怎么做?
  • u.id = 100 是主键等值查询 → users 表是 常量表
  • 先查出用户100的信息;
  • 再用 o.user_id = 100orders 表查订单;
  • 整个过程极快。

结论:WHERE 中的主键/唯一索引等值查询会被优先处理。


三、ORDER BY 优化(避免 filesort)

✅ 核心思想:

让索引的物理顺序匹配 ORDER BY 的逻辑顺序,避免额外排序。


🔹 案例5:利用复合索引避免排序

-- 索引
CREATE INDEX idx_city_age ON users(city, age);-- 查询:某城市用户按年龄升序排列
SELECT name, age FROM users
WHERE city = 'Beijing'
ORDER BY age ASC;
❓ 是否需要 filesort?
  • ✅ 不需要!
  • 因为 city='Beijing' 锁定了索引的一个范围;
  • 在这个范围内,age 已经有序。

结论:WHERE 固定前导列 + ORDER BY 后续列 = 可用索引排序。


🔹 案例6:混合排序方向(MySQL 8.0 降序索引)

-- 创建降序索引
CREATE INDEX idx_age_desc_name_asc ON users(age DESC, name ASC);-- 查询:按年龄倒序,同龄人按名字正序
SELECT name, age FROM users
ORDER BY age DESC, name ASC;
❓ 是否需要 filesort?
  • ✅ 不需要!索引方向完全匹配。
  • 如果没有这个索引,即使有 (age, name) 升序索引,也可能需要反向扫描或 filesort。

结论:MySQL 8.0 的 降序索引 极大提升了复杂排序的性能。


四、GROUP BY 优化(避免临时表 + filesort)

✅ 核心思想:

利用索引的有序性直接分组,而不是先把所有数据拉出来再分。


🔹 案例7:Loose Index Scan(松散索引扫描)

-- 索引
CREATE INDEX idx_city_age ON users(city, age);-- 查询:统计每个城市的最小年龄
SELECT city, MIN(age) FROM users
GROUP BY city;
❓ 如何执行?
  • 优化器使用 Loose Index Scan
    • 沿着索引走,遇到新的 city 就开启新组;
    • 当前组的第一条记录的 age 就是最小值;
  • ✅ 无需加载所有行,无需临时表。

结论:适用于聚合函数如 MIN()MAX(),且分组列是索引前缀。


🔹 案例8:Tight Index Scan(紧密索引扫描)

-- 查询:北京各年龄段人数
SELECT age, COUNT(*) FROM users
WHERE city = 'Beijing'
GROUP BY age;
❓ 执行方式?
  • 使用 idx_city_age 索引;
  • 扫描 city='Beijing' 的所有记录;
  • 因为 age 在这部分中有序,可以直接分组计数;
  • ✅ 避免了显式排序。

结论:即使不能跳跃,只要有序,就能高效分组。


五、子查询优化

🔹 案例9:IN 子查询 → Semi-Join 优化

-- 查询:有订单的用户信息
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders);
❓ 优化器怎么做?
  • 默认尝试 Semi-Join
    • 将子查询与外层合并,避免重复;
    • 可能使用物化(Materialization)缓存子查询结果;
  • 更高效,且可利用索引。

对比:如果写成 NOT IN,可能退化为 Anti-Semi-Join,需注意 NULL 值陷阱。


六、COUNT(*) 优化

🔹 案例10:MyISAM vs InnoDB

SELECT COUNT(*) FROM users;
存储引擎性能
MyISAM⚡ 极快,直接从元数据读取行数
InnoDB🐢 较慢,需扫描聚簇索引(但会选最小索引)

建议:对大表频繁 COUNT,可考虑用缓存或计数器表。


七、WHERE/HAVING 优化

🔹 案例11:表达式下推与 IS NULL 优化

SELECT * FROM users
WHERE age IS NULL;
  • ✅ 可使用索引(如果 age 有索引);
  • 优化器将其视为一种“等值查询”;
  • 访问类型可能是 refrange

❌ 对比:WHERE age + 1 = 10 → 无法使用索引,必须全表扫描。


🏁 终极总结:MySQL SELECT 优化 Checklist

类别最佳实践错误做法
索引设计建立复合索引 (a,b,c) 支持 WHERE a=... ORDER BY b只给每个列单独建索引
ORDER BY让排序列是索引最左前缀或后续列对表达式排序 ORDER BY UPPER(name)
GROUP BY分组列是索引前缀,用 MIN/MAX 利用 Loose ScanGROUP BY b 但索引是 (a,b) 且未过滤 a
JOIN小表驱动大表,ON 条件有索引多表 JOIN 无索引,导致 BNL 和临时表
子查询使用 EXISTS 替代 IN(尤其大数据量)NOT IN 子查询包含 NULL 值
COUNT大表 COUNT 考虑缓存或近似值直接 COUNT(*) 查千万级表
表达式避免在列上使用函数:WHERE YEAR(date)=2024WHERE date BETWEEN ...WHERE DATE(create_time) = '2024-01-01'
工具EXPLAIN 分析执行计划写完 SQL 就跑,不管性能
版本升级到 MySQL 8.0+ 享受 Skip Scan、降序索引等新特性停留在 5.7,错过重要优化

🛠️ 实战建议:如何分析一条慢查询?

  1. 使用 EXPLAIN 查看执行计划

    EXPLAIN FORMAT=JSON SELECT ...
    
  2. 关注:

    • type: 是否为 ALL(全表扫描)?
    • key: 是否用了索引?
    • rows: 扫描行数是否过多?
    • Extra: 是否有 Using filesort, Using temporary
  3. 优化策略

    • 添加合适的复合索引;
    • 重写 SQL 避免函数操作;
    • 拆分复杂查询;
    • 考虑分区或缓存。

如果你提供一条具体的慢 SQL,我可以帮你一步步分析并给出优化方案。希望这份 “原理 + 案例 + 总结”三位一体 的指南能真正帮你掌握 MySQL 查询优化!

http://www.dtcms.com/a/449738.html

相关文章:

  • PHP Directory:全面解析与优化实践
  • 网站开发实训报告参考文献网站丢了数据库还在
  • securinets ctf quals 2025 web all
  • 基于jsp的网站开发开题报告企业推广方式隐迅推知名
  • asp商品网站源码电影网站制作模版
  • 微服务注册与监听
  • 网站需要审核吗外贸电商平台哪个网站最好
  • 一个网站如何做cdn加速器ps平面设计主要做什么
  • 前端测试模块
  • 从零开始构建HIDS主机入侵检测系统:Python Flask全栈开发实战
  • 做网站收费吗重庆网站建设方案
  • 网站无法打开的原因多个网站给一个网站推广
  • 瞥[信号与系统个人笔记]第二章 连续时间信号与系统的时域分析W
  • cesium126,230130,Editing Tileset Materials 编辑瓦片集材质,官方教程:
  • 医院网站加快建设方案汽车网站建设公司哪家好
  • 从视口到容器:CSS 容器查询完全指南
  • 制作网站设计的技术有cms网站群
  • hpatch 学习笔记系列
  • 操作系统应用开发(二十五)RustDesk 502错误—东方仙盟筑基期
  • 欧美一级A做爰片成电影网站装企营销网站建设
  • 一张图入门 Docker
  • Spring AI alibaba 智能体扩展
  • leetcode 130 被围绕的区域
  • AiCube图形化程序自动生成【SPI,SPI-DMA,I2C,I2C-DMA】代码,驱动OLED-12864
  • Java 变量类型
  • 怎么修改网站源文件高明网站设计多少钱
  • 第14节-增强表结构-Renaming-columns
  • 网站开发长沙免费国内linux服务器
  • 276-基于Python的爱奇艺视频数据可视化分析系统
  • Kubernetes容器运行时:cri-docker vs containerd