引言:去重操作,你真的用对了吗?
“为什么同样的去重需求,同事的代码运行如飞,你的却慢如蜗牛?”
一位开发者在处理百万级数据时,发现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:分组聚合的多面手
核心功能:按指定字段分组,通常结合聚合函数(如 COUNT,SUM)使用。典型场景: -- 统计每个城市的用户数
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;全表扫描:读取所有数据到内存或磁盘临时表。 排序去重:对临时表按所有字段排序,遍历去除重复行。 资源消耗:若数据量超过 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;索引扫描:直接遍历 city字段的B+树索引(有序)。跳跃去重:利用索引有序性,跳过重复值,无需全量排序。 零回表:若仅查询分组字段,直接从索引取数据,无需访问原表。
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 | ||
SELECT city FROM users GROUP BY city | 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 INT, ADD 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 }
}
}
}将去重字段(如 city)同步到Elasticsearch。使用 terms aggregation实现毫秒级去重:
五、灵魂拷问:你真的需要去重吗?
场景1:仅统计不重复数量 → SELECT COUNT(DISTINCT city)。场景2:需要列出所有不重复值 → 优先用 GROUP BY+索引。场景3:去重后需复杂计算 → 使用窗口函数(如 ROW_NUMBER())。
结尾互动
你在使用GROUP BY和DISTINCT时踩过哪些坑?欢迎留言分享!
THE END
匿名
2025-11-09
https://collaigo.com 免费在线拼图工具
匿名
2025-10-22
盖楼盖楼!
匿名
2025-08-11
沙发沙发
匿名
2025-08-10
https://at.oiik.cn/bing.html
匿名
2025-02-21
实用,我在开发https://minmail.app/时候使用到了
王飞翔
2024-12-30
亲爱的朋友:您好!中国疫情持续蔓延,很多人症状非常严重持久不愈,医院人满为患,各年龄段随地倒猝死的现象暴增,多省感染手足口、甲流、乙流、支原体、合胞及腺病毒的儿童不断攀升,目前各种天灾人祸,天气异象频发。古今中外的很多预言都说了这几年人类有大灾难,如刘伯温在预言中说 “贫者一万留一千,富者一万留二三”,“贫富若不回心转,看看死期到眼前”, 预言中也告诉世人如何逃离劫难的方法,真心希望您能躲过末劫中的劫难,有个美好的未来,请您务必打开下方网址认真了解,内有躲避瘟疫保平安的方法。网址1:https://github.com/1992513/www/blob/master/README.md?abhgc#1 网址2:bitly.net/55bbbb 网址3:https://d3ankibxiji86m.cloudfront.net/30gj 如打不开请多换几个浏览器试