Mysql篇:MySQL distinct 与 group by 去重(where/having)

Mysql篇:MySQL distinct 与 group by 去重(where/having)

一、背景与问题

在数据处理场景中,重复数据是常见的问题。MySQL 提供了 DISTINCT 和 GROUP BY 两种去重机制,但二者在实现原理、性能表现和适用场景上有显著差异。本文将从底层原理出发,结合真实开发场景,深入分析两者的使用方式、常见误区和性能优化策略。

二、基本原理

1. DISTINCT 的工作原理

DISTINCT 是 SQL 中用于去重的关键词,其核心机制是:

  • 在查询结果集生成阶段,对字段值进行去重处理
  • 内部实现依赖于临时表(temporary table)和排序(sort)操作
  • 默认按全字段进行比较(包括 NULL 值)

2. GROUP BY 的工作原理

GROUP BY 是聚合操作的核心,其处理流程包括:

  • 按指定字段进行分组
  • 内部使用哈希表(hash table)或排序(sort)进行分组
  • 需配合聚合函数(如 COUNT、SUM、MAX 等)使用
  • 可通过 HAVING 子句进行分组过滤

3. 两者的本质区别

特性DISTINCTGROUP BY
去重目的生成唯一值列表生成分组统计结果
是否需要聚合函数不需要必须配合聚合函数使用
执行计划使用临时表+排序使用哈希/排序+分组
性能影响可能产生全表扫描可能产生全表扫描
灵活性仅能去重,不能做统计可同时做去重和统计

三、环境准备

-- 创建测试表
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;

-- 创建订单表
CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    customer_id INT,
    product_name VARCHAR(100),
    quantity INT,
    price DECIMAL(10,2),
    order_date DATE
);

-- 插入测试数据
INSERT INTO orders (order_id, customer_id, product_name, quantity, price, order_date)
VALUES
(1, 101, 'Laptop', 2, 1299.99, '2023-01-15'),
(2, 102, 'Monitor', 1, 499.99, '2023-01-15'),
(3, 101, 'Laptop', 1, 1299.99, '2023-01-16'),
(4, 103, 'Keyboard', 3, 89.99, '2023-01-16'),
(5, 102, 'Monitor', 2, 499.99, '2023-01-17'),
(6, 101, 'Laptop', 2, 1299.99, '2023-01-18'),
(7, 104, 'Mouse', 1, 29.99, '2023-01-18'),
(8, 105, 'Speaker', 1, 199.99, '2023-01-19'),
(9, 101, 'Laptop', 1, 1299.99, '2023-01-20');

四、核心实现

1. 基础去重:DISTINCT

-- 查询所有不重复的客户ID
SELECT DISTINCT customer_id FROM orders;

执行计划分析:

  • MySQL 会创建一个临时表,将 customer_id 字段去重后存储
  • 如果未指定索引,可能进行全表扫描
  • 对于大数据量,建议在 customer_id 字段建立索引

优化建议:

-- 建立索引
CREATE INDEX idx_customer_id ON orders(customer_id);

2. 分组统计:GROUP BY

-- 查询每个客户的总订单金额
SELECT customer_id, SUM(price * quantity) AS total
FROM orders
GROUP BY customer_id;

执行计划分析:

  • 使用哈希分组(hash group by)或排序分组(sort group by)
  • 如果未指定索引,可能进行全表扫描
  • 聚合函数会计算每个分组的统计值

优化建议:

-- 建立复合索引
CREATE INDEX idx_customer_product ON orders(customer_id, product_name);

3. 条件过滤:WHERE 与 HAVING

-- 查询购买金额超过 5000 的客户
SELECT customer_id, SUM(price * quantity) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(price * quantity) > 5000;

关键点:

  • WHERE 用于过滤原始数据行
  • HAVING 用于过滤分组后的结果
  • HAVING 可以使用聚合函数进行条件判断

五、完整案例

场景:统计每个产品的销售总量

-- 创建产品表
CREATE TABLE products (
    product_id INT PRIMARY KEY,
    product_name VARCHAR(100)
);

-- 插入产品数据
INSERT INTO products (product_id, product_name)
VALUES
(1, 'Laptop'),
(2, 'Monitor'),
(3, 'Keyboard'),
(4, 'Mouse'),
(5, 'Speaker');

-- 统计每个产品的销售总量
SELECT p.product_name, SUM(o.quantity) AS total_sold
FROM orders o
JOIN products p ON o.product_name = p.product_name
GROUP BY p.product_name
ORDER BY total_sold DESC;

结果分析:

  • Laptop 销量最高,共 5 个
  • Monitor 销量次之,共 3 个
  • 其他产品销量较低

性能优化:

  • 建立联合索引:CREATE INDEX idx_product ON orders(product_name, quantity)
  • 使用子查询预处理数据:SELECT product_name, SUM(quantity) FROM orders GROUP BY product_name

六、源码解析

以 MySQL 8.0 源码为例,GROUP BY 的处理流程如下:

  1. 解析阶段:optimizer::optimize() 处理 GROUP BY 语句
  2. 执行计划生成:JOIN::make_join_plan() 生成分组计划
  3. 分组执行:

    • 如果使用 GROUP BY 列作为索引,使用哈希分组
    • 否则进行全表扫描并排序分组
  4. 聚合计算:item_sum::walk() 执行聚合函数计算

对于 DISTINCT,其核心逻辑在 sql_select.cc 中,通过 make_distinct() 函数生成临时表并去重。

七、进阶使用

1. 多字段去重

-- 查询不重复的客户和产品组合
SELECT DISTINCT customer_id, product_name
FROM orders
WHERE order_date > '2023-01-15';

2. 窗口函数替代方案

-- 使用窗口函数实现去重
SELECT customer_id, product_name
FROM (
    SELECT 
        customer_id, 
        product_name,
        ROW_NUMBER() OVER (PARTITION BY customer_id, product_name ORDER BY order_id) AS rn
    FROM orders
) t
WHERE rn = 1;

3. 联合去重

-- 联合两个表的去重查询
SELECT DISTINCT customer_id, product_name
FROM orders
UNION
SELECT customer_id, product_name
FROM returns;

八、性能与工程实践

1. 性能优化策略

场景优化方法
大表去重使用 DISTINCT + 索引
分组统计建立复合索引 + 聚合函数优化
多条件过滤使用 WHERE 预过滤 + HAVING 精确过滤
联合查询使用 UNION 替代 OR 条件

2. 索引设计建议

  • 对 GROUP BY 字段建立索引
  • 对 DISTINCT 字段建立索引
  • 对 WHERE 条件字段建立索引
  • 对 JOIN 字段建立复合索引

3. 异常处理

-- 处理空值的特殊处理
SELECT customer_id, SUM(price * quantity) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(price * quantity) IS NOT NULL;

4. 安全风险

  • 避免使用 GROUP BY 的 SELECT *,可能导致数据泄露
  • 对敏感字段使用 HAVING 进行过滤
  • 使用参数化查询防止 SQL 注入

九、常见问题与踩坑

1. 常见错误示例

-- 错误:GROUP BY 中未使用聚合字段
SELECT customer_id, product_name
FROM orders
GROUP BY customer_id;

错误原因:product_name 未被聚合函数处理

2. 常见错误:DISTINCT 与 GROUP BY 混用

-- 错误:同时使用 DISTINCT 和 GROUP BY
SELECT DISTINCT customer_id, product_name
FROM orders
GROUP BY customer_id;

错误原因:GROUP BY 已经完成分组,DISTINCT 无实际意义

3. 常见错误:HAVING 使用不当

-- 错误:HAVING 中使用非聚合字段
SELECT customer_id, SUM(price * quantity) AS total
FROM orders
GROUP BY customer_id
HAVING order_date > '2023-01-15';

错误原因:order_date 不在 GROUP BY 或聚合函数中

十、最佳实践

1. 使用场景选择指南

场景推荐方案
简单去重DISTINCT
分组统计GROUP BY + 聚合函数
条件过滤WHERE + HAVING
复杂聚合窗口函数 + 子查询
多表联合UNION + 索引优化

2. 索引使用建议

  • 对 GROUP BY 字段建立索引
  • 对 DISTINCT 字段建立索引
  • 对 WHERE 条件字段建立索引
  • 对 JOIN 字段建立复合索引

3. 性能调优技巧

  • 使用 EXPLAIN 分析执行计划
  • 通过 SHOW PROFILES 分析查询耗时
  • 使用 SHOW ENGINE INNODB STATUS 分析锁问题
  • 对大数据量使用分页查询(LIMIT + OFFSET)

十一、总结

DISTINCT 和 GROUP BY 是 MySQL 中处理重复数据的核心工具,但二者在实现原理、适用场景和性能表现上有显著差异。在实际开发中需要根据具体需求选择合适的方法:

  • 使用 DISTINCT 进行简单去重时,注意索引优化
  • 使用 GROUP BY 进行分组统计时,合理使用聚合函数
  • 在需要条件过滤时,结合 WHERE 和 HAVING 使用
  • 对大数据量查询,注意分页和索引设计
  • 避免滥用 SELECT *,防止数据泄露

通过合理使用这些技术,可以有效提升数据处理的效率和准确性,同时确保系统的稳定性和安全性。在实际项目中,建议根据具体业务需求进行充分测试和性能调优,以达到最佳效果。

最后修改于:2026年09月19日 13:39

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日