为什么GROUP BY比DISTINCT快3倍?90%的程序员都踩过这个坑!

云游道人 2025-05-14 772 阅读 0评论

引言:去重操作,你真的用对了吗?

“为什么同样的去重需求,同事的代码运行如飞,你的却慢如蜗牛?”

一位开发者在处理百万级数据时,发现SELECT DISTINCT耗时高达10秒,而改用GROUP BY后仅需3秒——性能差距超过3倍

90%的程序员误以为两者功能完全相同,但背后的机制天差地别。本文将彻底揭秘它们的核心区别,并教你如何避免性能陷阱!


一、功能解析:GROUP BY和DISTINCT的职责边界

1. DISTINCT:简单粗暴的去重工具
  • 核心功能:对查询结果的所有列进行全局去重。
  • 典型场景
    -- 查询所有不重复的城市  
    SELECT DISTINCT city FROM users;  
    -- 查询不重复的城市和性别组合  
    SELECT DISTINCT city, gender FROM users;  
  • 本质:将结果集加载到临时表,排序后去除重复行。
2. GROUP BY:分组聚合的多面手
  • 核心功能:按指定字段分组,通常结合聚合函数(如COUNTSUM)使用。
  • 典型场景
    -- 统计每个城市的用户数  
    SELECT city, COUNT(*) FROM users GROUP BY city;  
    -- 计算每个部门的平均薪资  
    SELECT department, AVG(salary) FROM employees GROUP BY department;  
  • 隐藏技能:当不搭配聚合函数时,可实现类似DISTINCT的去重效果。

二、本质区别:为何GROUP BY能吊打DISTINCT?

1. 执行计划对比:临时表与索引的生死博弈
  • DISTINCT的执行流程(无索引时):

    -- EXPLAIN结果:Using temporary; Using filesort  
    EXPLAIN SELECT DISTINCT city FROM users;  
    1. 全表扫描:读取所有数据到内存或磁盘临时表。
    2. 排序去重:对临时表按所有字段排序,遍历去除重复行。
    3. 资源消耗:若数据量超过tmp_table_size,临时表写入磁盘,触发I/O风暴。
  • GROUP BY的执行流程(有索引时):

    -- 添加索引  
    ALTER TABLE users ADD INDEX idx_city (city);  

    -- EXPLAIN结果:Using index(无临时表,无排序)  
    EXPLAIN SELECT city FROM users GROUP BY city;  
    1. 索引扫描:直接遍历city字段的B+树索引(有序)。
    2. 跳跃去重:利用索引有序性,跳过重复值,无需全量排序。
    3. 零回表:若仅查询分组字段,直接从索引取数据,无需访问原表。
2. 性能差距的三大核心原因
  • 原因1:索引利用率
    • GROUP BY可利用索引跳过排序和临时表,而DISTINCT即便有索引也可能触发全表扫描。
  • 原因2:内存与磁盘的较量
    • DISTINCT的临时表默认使用磁盘存储(MyISAM引擎),GROUP BY优先使用内存(MEMORY引擎)。
  • 原因3:隐式排序的代价
    SELECT city FROM users GROUP BY city ORDER BY NULL-- 性能再提升20%  
    • DISTINCT必须全排序,而GROUP BY可通过ORDER BY NULL禁用排序:
3. 实测数据:千万级表性能对比


查询语句
无索引耗时
有索引耗时
SELECT DISTINCT city FROM users
12.3s
9.8s
SELECT city FROM users GROUP BY city
11.7s
2.1s


  • 结论
    • 无索引时:两者性能接近,但GROUP BY略优(省去显式排序)。
    • 有索引时GROUP BY性能提升超过3倍!

三、避坑指南:这些场景GROUP BY反而更危险!

1. 分组字段无索引时
  • 问题GROUP BY同样需要全表扫描+临时表,性能与DISTINCT无异。
  • 优化方案
    -- 为分组字段添加索引  
    ALTER TABLE orders ADD INDEX idx_product (product_id);  
2. 查询包含非分组字段
  • 错误示例
    -- 查询结果不确定(MySQL可能随机返回user_id)  
    SELECT city, user_id FROM users GROUP BY city;  
  • 正确写法
    SELECT city, MAX(user_id) FROM users GROUP BY city; -- 明确聚合规则  
3. 分组的字段含复杂计算
  • 错误示例
    -- 索引失效,触发全表扫描  
    SELECT YEAR(create_time) FROM orders GROUP BY YEAR(create_time);  
  • 优化方案
    -- 新增冗余字段并添加索引  
    ALTER TABLE orders ADD COLUMN create_year INTADD INDEX idx_year (create_year);  
    UPDATE orders SET create_year = YEAR(create_time);  

四、终极实战:亿级数据去重优化方案

1. 索引设计黄金法则
  • 覆盖索引:确保查询字段全部包含在索引中。
    -- 为city和gender创建联合索引  
    ALTER TABLE users ADD INDEX idx_city_gender (city, gender);  
    -- 以下查询直接走索引  
    SELECT city, gender FROM users GROUP BY city, gender;  
2. 参数调优:强制临时表驻留内存
-- 调整会话级内存参数(单位:字节)  
SET tmp_table_size = 256 * 1024 * 1024;  -- 256MB  
SET max_heap_table_size = 256 * 1024 * 1024;  
3. 分布式方案:Elasticsearch预聚合
  • 适用场景:实时统计海量数据的唯一值。
  • 操作步骤
    GET /users/_search  
    {  
      "size"0,  
      "aggs": {  
        "unique_cities": {  
          "terms": { "field""city.keyword""size"1000 }  
        }  
      }  
    }  
    1. 将去重字段(如city)同步到Elasticsearch。
    2. 使用terms aggregation实现毫秒级去重:

五、灵魂拷问:你真的需要去重吗?

  • 场景1:仅统计不重复数量 → SELECT COUNT(DISTINCT city)
  • 场景2:需要列出所有不重复值 → 优先用GROUP BY+索引
  • 场景3:去重后需复杂计算 → 使用窗口函数(如ROW_NUMBER())。

结尾互动

你在使用GROUP BY和DISTINCT时踩过哪些坑?欢迎留言分享!

喜欢就支持以下吧
点赞 0

发表评论

快捷回复: 表情:
aoman baiyan bishi bizui cahan ciya dabing daku deyi doge fadai fanu fendou ganga guzhang haixiu hanxiao zuohengheng zhuakuang zhouma zhemo zhayanjian zaijian yun youhengheng yiwen yinxian xu xieyanxiao xiaoku xiaojiujie xia wunai wozuimei weixiao weiqu tuosai tu touxiao tiaopi shui se saorao qiudale qinqin qiaoda piezui penxue nanguo liulei liuhan lenghan leiben kun kuaikule ku koubi kelian keai jingya jingxi jingkong jie huaixiao haqian aini OK qiang quantou shengli woshou gouyin baoquan aixin bangbangtang xiaoyanger xigua hexie pijiu lanqiu juhua hecai haobang caidao baojin chi dan kulou shuai shouqiang yangtuo youling
提交
评论列表 (有 0 条评论, 772人围观)

最近发表

热门文章

最新留言

热门推荐

标签列表