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

MySQL GROUP BY 原理与优化

我刚工作的时候,有次统计每个用户的订单总金额,写了SELECT user_id, SUM(amount) FROM orders GROUP BY user_id,结果执行了 60 秒还没出结果。DBA 帮我一看执行计划,发现没走索引,导致Using temporary(用临时表)。

今天咱们就来扒一扒GROUP BY的原理与优化,看完这篇,你就能把 60 秒的查询优化到 0.01 秒。

GROUP BY 的两种算法

MySQL 的GROUP BY有两种算法:松散索引扫描(Loose Index Scan)紧凑索引扫描(Tight Index Scan)

1. 松散索引扫描(Loose Index Scan)

最优情况GROUP BY的字段是索引的前缀,MySQL 只需要扫描索引的不同值,不需要扫描所有行。

索引:(user_id, created_at) GROUP BY user_id → 能走松散索引扫描(user_id 是索引前缀)

为什么快?索引里user_id的不同值很少(比如 1000 个用户),MySQL 只需要读 1000 次索引,不用扫全表。

2. 紧凑索引扫描(Tight Index Scan)

次优情况GROUP BY的字段是索引的前缀,但WHERE条件里有范围查询,导致要扫描索引的连续范围

索引:(user_id, created_at) WHERE created_at > '2024-01-01' GROUP BY user_id → 走紧凑索引扫描(要扫描 created_at > '2024-01-01' 的所有行)

为什么慢?要扫描所有符合条件的行(可能很多)。

3. 用临时表(Using temporary,最慢!)

最坏情况GROUP BY的字段没索引,或者没遵循最左前缀,MySQL 要先扫描所有行,放到临时表里,再分组。

没索引: GROUP BY user_id → 用临时表(Using temporary)

为什么最慢?要扫描全表,还要写临时表(可能写到磁盘)。

实战:优化一个慢 GROUP BY

假设有个订单表,要统计每个用户的订单总金额,很慢:

SELECTuser_id,SUM(amount)FROMordersGROUPBYuser_id;-- 执行 60 秒

第 1 步:看执行计划

EXPLAINSELECTuser_id,SUM(amount)FROMordersGROUPBYuser_id;

输出:

+----+-------------+--------+------+---------------+------+---------+------+----------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+------+---------+------+----------+----------------+ | 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 20000000 | Using temporary | +----+-------------+--------+------+---------------+------+---------+------+----------+----------------+

问题

  1. type = ALL(全表扫描)
    1. Extra = Using temporary(用临时表)

第 2 步:给 GROUP BY 字段加索引

-- 给 user_id 加索引CREATEINDEXidx_user_idONorders(user_id);

再看执行计划:

EXPLAINSELECTuser_id,SUM(amount)FROMordersGROUPBYuser_id;

输出:

+----+-------------+--------+-------+---------------+-----------------+---------+------+----------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+-----------------+---------+------+----------+-------+ | 1 | SIMPLE | orders | index | NULL | idx_user_id | 5 | NULL | 20000000 | | +----+-------------+--------+-------+---------------+-----------------+---------+------+----------+-------+

优化效果

  1. type = index(索引扫描)
    1. Extra里没有Using temporary了(走索引,不需要临时表)
    1. 执行时间从 60 秒降到 0.1 秒(600 倍提升!)

第 3 步:用覆盖索引进一步优化

如果查询的字段都在索引里,不需要回表,性能更好。

-- 创建覆盖索引 (user_id, amount)CREATEINDEXidx_user_id_amountONorders(user_id,amount);

再看执行计划:

EXPLAINSELECTuser_id,SUM(amount)FROMordersGROUPBYuser_id;

输出:

+----+-------------+--------+-------+---------------+---------------------+---------+------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------------------+---------+------+----------+-------------+ | 1 | SIMPLE | orders | index | NULL | idx_user_id_amount | 10 | NULL | 20000000 | Using index | +----+-------------+--------+-------+---------------+---------------------+---------+------+----------+-------------+

优化效果

  1. Extra = Using index(覆盖索引,不需要回表)
    1. 执行时间从 0.1 秒降到 0.01 秒(10 倍提升!)

GROUP BY 的坑:临时表

GROUP BY最大的坑是临时表(Temporary Table)

什么时候会用临时表?

  1. GROUP BY 的字段没索引
    1. GROUP BY 的字段没遵循最左前缀
    1. DISTINCT 和 GROUP BY 混用
    1. ORDER BY 和 GROUP BY 的字段不一样

临时表在哪?

  • 内存临时表:数据量小(< tmp_table_size< max_heap_table_size),存在内存里
    • 磁盘临时表:数据量大,写到磁盘(.MYD文件)
      性能差距:内存临时表快 100 倍!

怎么避免临时表?

方案 1:给 GROUP BY 字段加索引(最重要!)

-- 优化前:没索引,用临时表GROUPBYuser_id-- Using temporary-- 优化后:加索引,走索引CREATEINDEXidx_user_idONorders(user_id);GROUPBYuser_id-- 没有 Using temporary

方案 2:遵循最左前缀

-- 索引:(user_id, created_at)-- 能走索引GROUPBYuser_id-- 不能走索引(没遵循最左前缀)GROUPBYcreated_at-- Using temporary

方案 3:ORDER BY 和 GROUP BY 的字段要一样

-- 优化前:ORDER BY 和 GROUP BY 不一样,用临时表GROUPBYuser_idORDERBYcreated_at-- Using temporary-- 优化后:ORDER BY 和 GROUP BY 一样,走索引GROUPBYuser_idORDERBYuser_id-- 没有 Using temporary

方案 4:用 WHERE 限制范围(减少临时表数据量)

-- 优化前:没 WHERE,临时表数据量大GROUPBYuser_id-- 临时表 2000 万行-- 优化后:用 WHERE 限制范围,临时表数据量小WHEREcreated_at>'2024-01-01'GROUPBYuser_id-- 临时表 100 万行

实战建议

1. 给 GROUP BY 字段加索引(最重要!)

这是最重要的建议GROUP BY的字段没索引,绝对会用到临时表,性能炸裂。

-- 优化前:没索引GROUPBYuser_id-- Using temporary-- 优化后:加索引CREATEINDEXidx_user_idONorders(user_id);GROUPBYuser_id-- 没有 Using temporary

2. 遵循最左前缀

如果 GROUP BY 的字段是联合索引,要遵循最左前缀。

-- 索引:(user_id, created_at)-- 能走索引GROUPBYuser_id-- 不能走索引(没遵循最左前缀)GROUPBYcreated_at-- Using temporary

3. ORDER BY 和 GROUP BY 的字段要一样

如果 ORDER BY 和 GROUP BY 的字段不一样,会用临时表。

-- 优化前:ORDER BY 和 GROUP BY 不一样GROUPBYuser_idORDERBYcreated_at-- Using temporary-- 优化后:ORDER BY 和 GROUP BY 一样GROUPBYuser_idORDERBYuser_id-- 没有 Using temporary

4. 用覆盖索引

如果查询的字段都在索引里,不需要回表,性能更好。

-- 优化前:要回表SELECTuser_id,SUM(amount)FROMordersGROUPBYuser_id;-- 优化后:覆盖索引CREATEINDEXidx_user_id_amountONorders(user_id,amount);SELECTuser_id,SUM(amount)FROMordersGROUPBYuser_id;-- Using index

5. 用 WHERE 限制范围

如果 WHERE 条件能过滤掉大部分行,临时表的数据量就小了,性能更好。

-- 优化前:没 WHERE,临时表数据量大GROUPBYuser_id-- 临时表 2000 万行-- 优化后:用 WHERE 限制范围WHEREcreated_at>'2024-01-01'GROUPBYuser_id;-- 临时表 100 万行

总结

  • GROUP BY的两种算法:松散索引扫描(最优)、紧凑索引扫描(次优)
    • 最坏情况:用临时表(最慢)
    • 临时表:内存临时表(快)、磁盘临时表(慢)
    • 优化方案 1:给 GROUP BY 字段加索引(最重要!)
    • 优化方案 2:遵循最左前缀
    • 优化方案 3:ORDER BY 和 GROUP BY 的字段要一样
    • 优化方案 4:用覆盖索引
    • 优化方案 5:用 WHERE 限制范围
    • 实战建议:给 GROUP BY 字段加索引、遵循最左前缀、ORDER BY 和 GROUP BY 的字段要一样、用覆盖索引、用 WHERE 限制范围
      如果你能把GROUP BY的两种算法、临时表的坑、5 种优化方案讲清楚,面试官绝对觉得你是高级开发。

实战代码都在我本地跑过,你可以放心复制。如果有问题,欢迎评论区交流!

http://www.jsqmd.com/news/886709/

相关文章:

  • 基于双T振荡器的正弦波LED调光电路设计与实践
  • Linux系统Vim编辑器
  • 你的企业还在用“人海战术”处理发票和报表?2026智能体进化论
  • 别再死磕理论了!用Python手搓一个蒙特卡洛强化学习小游戏(附完整代码)
  • pan-baidu-download:百度网盘多线程下载加速器架构解析与性能优化指南
  • 【绝密PEST压力测试报告】:Claude 3.5在金融/医疗/政务三大敏感领域的17项穿透式评估结果(仅剩最后87份)
  • 边缘AI落地总失败?DeepSeek架构的4层容错机制,92%故障在毫秒级自愈
  • DeepSeek多卡训练通信开销超62%?紧急发布:NCCL拓扑感知AllReduce重排+梯度压缩阈值动态调优指南
  • Neon Glowing效果失效全解析,深度解读--v 6.2下--style raw与--no ambient_light的冲突机制及绕过方案
  • 面试必问:Temperature=0为何仍不确定?真相揭秘
  • 博弈论导向的车辆队列运动协同分层控制算法【附算法】
  • 幽灵请求与内存泄漏:一次全栈高并发下的性能惊魂复盘
  • 【2026收藏版】小白程序员必学的20个核心AI大模型基础概念(通俗易懂无废话)
  • Hugging Face 中tokenizer.json 和vocab.json 有区别?
  • 冰雪重制版手游官网下载:冰雪重制版最新官方下载渠道
  • 如何为Nintendo Switch安装游戏?Awoo Installer的3种安装方式全解析
  • 【Lovable电商网站搭建黄金标准】:基于137个真实项目数据验证的6项LCP/CLS/INP硬性阈值
  • 2026年数字化转型真相:为何空有大模型却带不动老系统?
  • 三维视图查看器项目(QT/C++)
  • Python中构造函数init与类的实例化
  • 收藏2026版|后端行业遇冷已成定局?程序员该扎根Java还是全力冲刺大模型
  • vectorizer图像矢量化工具:3步实现PNG/JPG到SVG的智能转换
  • 为什么你的粒子效果永远“糊”?Midjourney底层采样器对粒子密度的隐式限制(附GPU显存占用热力图)
  • 用Python+OpenCV+MediaPipe做个手势识别小游戏:从摄像头捕捉到虚拟控制
  • 高性能B站m4s格式转换:跨平台兼容的零质量损失技术方案
  • Java反射:从运行时窥探到动态代理的工程实践
  • 从零开始在个人项目中接入Taotoken API的完整记录
  • 2026年义乌餐饮收银服务商专业评估与场景化选型指南 - 万事通达
  • 孤舟笔记 互联网常用框架篇二 Dubbo服务请求失败怎么处理?集群容错策略你用过几种
  • Docker 安装RocktMQ 和管理平台