用 GROUP BY + HAVING 查重复的核心是:先按指定字段分组,再用 HAVING 筛选 COUNT() > 1 的组;SELECT 中只能包含分组字段和聚合函数,不可直接 SELECT ;查完整重复行需结合子查询、JOIN 或窗口函数;注意 NULL 归组、索引有效性及执行计划优化。

怎么用 GROUP BY + HAVING 找出重复的某几列
核心思路是:把可能重复的字段组合起来分组,再筛出组内行数大于 1 的组。不是查“整行重复”,而是查“指定字段值完全相同的记录”——这更符合实际业务场景,比如查 email 重复、user_id 和 order_date 组合重复。
常见错误现象:SELECT * 直接跟 GROUP BY 混用,MySQL 8.0+ 默认报错(sql_mode 含 ONLY_FULL_GROUP_BY),因为非分组字段的值不明确。
- 只在
SELECT中放分组字段和聚合结果,例如:SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 - 如果想看具体哪些记录重复,得用子查询或
JOIN回原表,不能靠GROUP BY直接列出所有重复行 - 注意 NULL 值:多个
NULL在GROUP BY中被视为相同,会归为一组,这有时是预期行为,有时是坑
如何查出全部重复行(含完整字段)
单纯 GROUP BY 只能返回分组摘要,真要拿到每一条重复记录(比如删掉其中部分),得借助派生表或窗口函数。MySQL 8.0+ 推荐用 ROW_NUMBER(),5.7 及以前只能用自连接或相关子查询。
使用场景:运维清理脏数据、导出问题用户列表、ETL 前校验。
- MySQL 8.0+ 更清晰:
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users) t WHERE t.rn > 1 - 兼容旧版(慢但通用):
SELECT u1.* FROM users u1 INNER JOIN users u2 ON u1.email = u2.email AND u1.id > u2.id—— 这种写法只匹配“后面出现的重复项”,避免自连自己 - 性能影响:大表上
JOIN或窗口函数都可能触发全表扫描,务必确保email等分组字段有索引
HAVING 和 WHERE 的区别到底在哪
WHERE 过滤的是“单行”,HAVING 过滤的是“分组后结果”。很多人写成 WHERE COUNT(*) > 1,直接报错,因为 COUNT(*) 是聚合结果,还没分组完就不存在。
- 必须先
GROUP BY,才能用HAVING;没GROUP BY时,HAVING行为等价于WHERE,但语义混乱,别这么写 -
HAVING可以引用聚合函数(COUNT、MAX、AVG),WHERE不可以 - 执行顺序是:
WHERE→GROUP BY→HAVING→ORDER BY,所以WHERE越早过滤掉无用行,GROUP BY越快
为什么加了索引还是慢?几个隐蔽瓶颈
加了 INDEX(email) 不代表 GROUP BY email 就一定快。MySQL 的执行计划可能仍走全表扫描,尤其当重复率高、分组数多时。
- 检查是否用了索引:用
EXPLAIN SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1,看type是不是range或ref,而不是ALL - 如果
email字段长度很长(比如VARCHAR(255)),而你只建了前缀索引(INDEX(email(191))),GROUP BY可能无法利用该索引 -
HAVING COUNT(*) > 1无法下推到存储引擎层,意味着 MySQL 必须算出所有分组的计数,再过滤,大数据量时内存和临时表压力明显
真正难的不是写出语句,而是判断“这个重复查询到底该不该在数据库里跑”——有时候导出到 Python 用 pandas 去重,反而更快更稳。


















