SQL 去重效率天差地别?GROUP BY 和 DISTINCT 的底层厮杀

1,194 阅读3分钟

GROUP BYDISTINCT 是 SQL 中用于去重的两种方式,但它们的核心用途、底层原理和性能表现差异显著。以下是详细对比:

1. 核心用途差异

功能GROUP BYDISTINCT
主要目的分组聚合(如计算每组的 SUMCOUNT返回结果集的唯一行(去重)
聚合函数必须搭配聚合函数(如 SUMAVG不支持聚合函数,仅返回原始字段
示例GROUP BY dept_id + SUM(salary)DISTINCT city

2. 底层执行原理

GROUP BY 的执行流程

  1. 分组阶段
    • 按照 GROUP BY 指定的字段对结果集进行排序(如果未使用索引)。
    • 将相同值的行放入同一个组中。
  2. 聚合阶段
    • 对每个组应用聚合函数(如 SUMCOUNT),生成单行结果。
  3. 索引优化
    • 如果 GROUP BY 字段有索引,数据库会直接利用索引的有序性快速分组(避免排序)。
    • 例如,MySQL 的 filesort 操作可被索引避免。

DISTINCT 的执行流程

  1. 排序/哈希去重
    • 排序法:对结果集按所有选择的字段排序,相邻重复行只保留一行。
    • 哈希法:构建哈希表,遍历结果集时丢弃哈希冲突的重复行。
  2. 索引优化
    • 如果 SELECT 的字段包含索引,数据库直接扫描索引(无需回表)。
    • 例如,SELECT DISTINCT email 可通过 email 索引快速去重。

3. 性能对比

场景GROUP BYDISTINCT
无聚合的简单去重需额外排序开销,性能较差专为去重优化,性能更好
带聚合函数的分组直接分组聚合,性能最优无法实现,必须用 GROUP BY
索引优化依赖索引避免排序(如覆盖索引)依赖索引避免回表(如唯一索引)
大数据量去重可能触发磁盘临时文件(排序溢出)哈希法更高效(内存足够时)

4. 示例对比

假设有表 orders

user_idproductamount
1A100
1B200
2A150

场景1:统计每个用户的总金额(聚合)

SELECT user_id, SUM(amount)  -- 必须用 GROUP BY
FROM orders
GROUP BY user_id;
  • 结果
    user_idSUM(amount)
    1300
    2150

场景2:列出所有购买过的产品(去重)

SELECT DISTINCT product;  -- 简洁写法
-- 等价于(但性能更差):
SELECT product FROM orders GROUP BY product;
  • 结果
    product
    A
    B

5. 进阶区别

  1. NULL 处理

    • DISTINCT 将所有 NULL 视为相同值(只保留一个)。
    • GROUP BY 同样将 NULL 分为一组(如果字段允许为 NULL)。
  2. 多字段组合

    • DISTINCT col1, col2 去重的是两个字段的组合值。
    • GROUP BY col1, col2 对组合字段分组,需搭配聚合函数。
  3. ORDER BY 的联用

    • DISTINCT 先去重,再按结果排序。
    • GROUP BY 先分组,聚合后按结果排序(索引可优化排序)。

6. 选择建议

需求最佳选择理由
简单去重(无聚合)DISTINCT语法简洁,性能优化更好
分组聚合(如统计、平均值)GROUP BY必须搭配聚合函数
多字段去重 + 排序DISTINCT + 索引利用索引避免排序
大数据量分组GROUP BY + 索引索引可避免 filesort

总结

  • GROUP BY 是为聚合而生,必须搭配聚合函数,底层通过排序或哈希实现分组。
  • DISTINCT 是纯粹的去重工具,直接丢弃重复行,语法更简洁。
  • 性能关键:合理使用索引(覆盖索引、唯一索引)可大幅提升两者效率。