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

直接结论:用 GROUP BY + HAVING COUNT(*) > 1 是最稳妥、兼容性最好的方式,但不能直接查出所有重复行——它只返回分组摘要,真要拿完整记录得再套一层。
为什么 SELECT * 和 GROUP BY 一起用会报错?
MySQL 8.0+ 默认开启 ONLY_FULL_GROUP_BY 模式,不允许在 SELECT 中出现未出现在 GROUP BY 子句里的非聚合字段。比如写 SELECT *, COUNT(*) FROM users GROUP BY email,MySQL 会直接报错,因为 id、name 这些字段在每组里可能有多个值,数据库不知道该选哪一个。
- 正确写法只能是:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 - 想看重复次数排序?加
ORDER BY COUNT(*) DESC - 注意
NULL:多个NULL会被归为同一组,COUNT(*)也会统计进去——这有时是预期行为,有时是坑
怎么查出所有重复行(含完整字段)?
上面的 GROUP BY 只告诉你“哪些 email 重复了”,但不告诉你“是哪几条记录”。要拿到原始行,必须关联回原表:
- MySQL 8.0+ 推荐窗口函数:
SELECT * FROM (SELECT *, COUNT(*) OVER (PARTITION BY email) AS cnt FROM users) t WHERE cnt > 1 - 旧版本通用写法(注意性能):
SELECT u1.* FROM users u1 INNER JOIN (SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1) u2 ON u1.email = u2.email - 避免用
WHERE email IN (SELECT ...),子查询结果多时容易退化成慢查询 - 如果表很大,确保
email字段上有索引,否则JOIN或窗口函数都可能触发全表扫描
HAVING 和 WHERE 到底能不能混用?
HAVING 不是 WHERE 的替代品,它只作用于分组后的结果集。常见错误是写成 WHERE COUNT(*) > 1,这语法上就不合法——COUNT(*) 是聚合结果,还没 GROUP BY 就不存在。
- 执行顺序固定:
WHERE→GROUP BY→HAVING→ORDER BY - 所以,先用
WHERE过滤掉明显不需要参与分组的行(比如status = 'active'),能大幅减少GROUP BY的计算量 -
HAVING可以用聚合函数(COUNT、MAX),WHERE不可以;没写GROUP BY时硬加HAVING,语义混乱,别这么干
真正麻烦的不是语法,而是 NULL 处理、索引失效和大表 JOIN 的隐性开销——哪怕加了索引,如果 GROUP BY 字段区分度极低(比如全是 'unknown'),照样慢。查之前先 EXPLAIN 看一眼执行计划更靠谱。











