最稳妥方式是GROUP BY+HAVING COUNT(*)>1,但仅返回分组摘要;要获取完整重复行需结合窗口函数或子查询JOIN,且必须确保分组字段有索引以避免全表扫描。

直接用 GROUP BY + HAVING COUNT(*) > 1 就能快速识别重复记录,其他写法要么报错、要么查不全。
为什么 HAVING 必须搭配 COUNT 才能查重复
“重复”不是单条记录的属性,而是分组后的统计结果。数据库执行顺序是:WHERE → GROUP BY → HAVING,而 COUNT(*) 只在分组后才有意义。常见错误是把 COUNT 塞进 WHERE,比如 WHERE COUNT(*) > 1,直接报错:ERROR: aggregate functions are not allowed in WHERE。
正确逻辑是:
- 先按目标字段(如
email)分组 - 再用
HAVING COUNT(*) > 1筛出“出现次数大于 1”的组 -
HAVING后不能引用非分组字段(如id),否则 MySQL 8.0+ 的ONLY_FULL_GROUP_BY模式会拒绝执行
GROUP BY + HAVING 能返回哪些数据
它只返回每组一条汇总行,字段必须是 GROUP BY 列或聚合函数结果。例如:
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
结果里只有 email 和计数,没有 id、name 等原始字段。这是设计使然,不是 bug —— 如果你需要所有重复行的完整记录,得换方法。
容易踩的坑:
- 误以为加了
SELECT *就能拿到所有重复行(语法错误,MySQL 直接报错) - 在
GROUP BY里漏掉关键字段,导致分组粒度太粗,把不该合并的也合了 - 忽略
NULL值:SQL 中NULL = NULL为 false,所以含NULL的行默认不会被归入同一组
要查出所有重复行的具体记录,该用什么
这时候 GROUP BY + HAVING 不够用了,得结合子查询或窗口函数。
推荐两种可靠写法:
- 子查询方式(兼容老版本):
SELECT * FROM users<br>WHERE (email) IN (<br> SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1<br>);
- 窗口函数方式(MySQL 8.0+/PostgreSQL/Oracle):
SELECT id, email, name,<br> ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn<br>FROM users<br>WHERE email IN (<br> SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1<br>);
结果中rn > 1的行就是重复副本,rn = 1是每组保留的第一条。
注意:窗口函数里 PARTITION BY 的字段必须和前面子查询中 GROUP BY 的完全一致,否则逻辑对不上。
性能与可读性怎么平衡
单纯识别“哪些值重复”,GROUP BY + HAVING 最快、最直观;但一旦要定位具体哪几条记录重复,就得引入额外扫描——子查询会查两次表,窗口函数需排序开销。如果表很大,建议给分组字段加索引(如 INDEX(email))。
真正容易被忽略的是业务语义:你定义的“重复”到底指整行相同,还是某几个业务字段组合相同?GROUP BY 字段列错了,后面全白忙。

















