深入分析mysql中group by、order by的工作原理

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!


一、GROUP BY 的工作原理

GROUP BY 用于按指定列对数据进行分组,通常结合聚合函数(如 SUM、COUNT、MAX 等)使用。其核心机制如下:

1. 执行流程
  • 无索引时的处理:

    1. 全表扫描:读取所有满足条件的行。
    2. 构建临时表 :将数据按 GROUP BY 列分组,存储分组键和中间聚合结果。
    3. 排序与聚合 :
      • 默认按分组字段隐式排序(类似 ORDER BY)。
      • 若未显式关闭排序(ORDER BY NULL),可能触发文件排序(Using filesort)。
    4. 返回结果:遍历临时表输出分组后的数据。
  • 有索引时的优化:

    • 松散索引扫描(Loose Index Scan) :
      • 当 GROUP BY 列是索引的最左前缀且无范围查询时,直接跳过索引中的重复值,仅读取每组的第一行。
      • 示例:索引 (a, b),查询 GROUP BY a。
    • 紧凑索引扫描(Tight Index Scan) :
      • 按索引顺序逐行读取数据,隐式利用索引有序性完成分组。
      • 示例:索引 (a, b),查询 GROUP BY a, b。
2. 性能优化策略
  • 索引设计:

    • 创建联合索引,确保 GROUP BY 列是索引的最左前缀。
    • 使用覆盖索引(包含所有查询字段)避免回表。
    • 示例:ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
  • 避免隐式排序:

    sql 复制代码
    SELECT category, COUNT(*) FROM products 
    GROUP BY category ORDER BY NULL; -- 禁用默认排序
  • 监控临时表:

    sql 复制代码
    SHOW STATUS LIKE 'Created_tmp%'; -- 查看临时表使用情况

二、ORDER BY 的工作原理

ORDER BY 用于对结果集排序,其执行效率高度依赖索引和内存管理。

1. 执行流程
  • 无索引时的处理:

    1. 全表扫描:读取所有满足条件的行。
    2. 排序缓冲区(sort_buffer) :
      • 若数据量小于 sort_buffer_size,在内存中排序(单路排序)。
      • 若数据量过大,使用磁盘临时文件进行多路归并排序(双路排序)。
    3. 返回结果:按排序后的顺序输出数据。
  • 有索引时的优化:

    • 若排序字段与索引顺序一致,直接按索引顺序读取数据(避免排序)。
    • 支持正向或反向扫描索引(MySQL 8.0+ 支持降序索引)。
2. 性能优化策略
  • 索引设计:

    • 排序字段与索引顺序一致(包括升降序)。
    • 示例:索引 (created_at DESC),查询 ORDER BY created_at DESC。
  • 调整排序缓冲区:

    ini 复制代码
    sort_buffer_size = 8M          -- 增大内存缓冲区
    max_length_for_sort_data = 8192 -- 控制单行数据长度,优先单路排序
  • 避免深分页:

    sql 复制代码
    -- 低效写法(扫描前100010行)
    SELECT * FROM logs ORDER BY id LIMIT 100000, 10;
    
    -- 高效写法(基于游标)
    SELECT * FROM logs WHERE id > 100000 ORDER BY id LIMIT 10;

三、GROUP BY 与 ORDER BY 的联合使用

当查询同时包含 GROUP BY 和 ORDER BY 时,需注意执行顺序和索引设计。

1. 执行顺序
  • 默认顺序 :先执行 GROUP BY 分组,再对分组结果排序。
  • 优化思路 :
    • 若 ORDER BY 字段与 GROUP BY 字段一致,可省略 ORDER BY(分组后默认有序)。
    • 若不一致,需确保索引覆盖分组和排序字段。
2. 优化示例
  • 场景 :统计每个用户的最新订单并按时间倒序输出。

    sql 复制代码
    -- 低效写法(临时表 + 排序)
    SELECT user_id, MAX(created_at) 
    FROM orders 
    GROUP BY user_id 
    ORDER BY MAX(created_at) DESC;
    
    -- 优化方案(覆盖索引 + 延迟关联)
    ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
    
    SELECT o.user_id, o.created_at 
    FROM orders o 
    INNER JOIN (
      SELECT user_id, MAX(created_at) AS max_time 
      FROM orders 
      GROUP BY user_id
    ) AS tmp ON o.user_id = tmp.user_id AND o.created_at = tmp.max_time 
    ORDER BY o.created_at DESC;

四、监控与分析工具

  1. EXPLAIN 解析执行计划:

    • type 字段:index 表示索引扫描,ALL 表示全表扫描。
    • Extra 字段:
      • Using index:覆盖索引。
      • Using temporary:使用临时表。
      • Using filesort:文件排序。
  2. 性能状态监控:

    sql 复制代码
    SHOW STATUS LIKE 'Sort%';       -- 查看排序统计
    SHOW STATUS LIKE 'Created_tmp%'; -- 查看临时表统计
  3. Profiling 工具:

    sql 复制代码
    SET SESSION profiling = 1;
    SELECT ...; -- 执行查询
    SHOW PROFILE CPU, BLOCK IO FOR QUERY 1;

五、核心优化总结

操作 无索引时的开销 有索引优化策略 关键参数
GROUP BY 临时表 + 文件排序 松散/紧凑索引扫描,覆盖索引 tmp_table_size, sql_mode
ORDER BY 内存/磁盘排序 索引顺序扫描,延迟关联分页 sort_buffer_size, max_length_for_sort_data
联合使用 双重临时表 + 排序 索引覆盖分组和排序字段 联合索引设计,查询重写

最终建议:

  • 索引为王 :为高频查询的 GROUP BY 和 ORDER BY 字段设计联合索引。
  • 避免全表扫描:通过覆盖索引减少 I/O 和排序开销。
  • 监控先行 :定期分析慢查询日志和 EXPLAIN 结果,针对性优化。
相关推荐
j7~2 分钟前
IT 面试指南:从准备到 Offer 的完整路线图(校招 / 实习通用
面试·职场和发展·秋招·春招·实习面试·it后端
Nuanyt5 分钟前
LeetCode Hot 100 · 双指针 3 题 Java
java·笔记·算法·leetcode·双指针
Wang's Blog11 分钟前
Java 项目实战: 交友项目 - 圈子互动
java·开发语言·交友
Rain的Java大神之路13 分钟前
北漂 13 年,我把 Java 家底钉成了这张知识图谱(全站免费)
java·后端·程序员
Amos_Web22 分钟前
通过 Rspack 源码学 Rust(一):`mod`、`pub` 与 `use crate`/`use super`
后端
张彦峰ZYF34 分钟前
拆得开不等于该拆开:微服务与模块化单体的边界判据、规模阈值、团队适配与一致性代价
后端·微服务·边界是否稳定·扩展天花板是否到顶·有无小组能独立值班·独立部署的自由会否被用上·这一刀是否切断不可降级的不变量
wuminyu35 分钟前
JVM规范学习总结-Class文件结构
java·linux·c语言·jvm·c++
小淮AI43 分钟前
面试前 48 小时,怎样练表达和复盘:一份个人准备记录
面试·职场和发展
是江迪呀1 小时前
介绍一个开源项目 —— 烂笔头
前端·后端
七夜zippoe1 小时前
Spring AI + Ollama 本地模型:数据不出内网的合规方案(落地全流程)
java·人工智能·spring·ai·ollama