最常用可靠的方法是group by分组+having count()>n,先按目标字段分组再筛选组内行数超n的组;count()比count(字段)更安全,因后者会忽略null值;多字段重复需在group by和having中保持字段一致;索引应与group by顺序匹配以提升性能。

用 GROUP BY + HAVING 统计重复次数
直接对目标字段分组,再用 HAVING 筛选出现次数 ≥ N 的组。这是最常用也最可靠的方式,避免了子查询或窗口函数的兼容性问题。
-
GROUP BY必须包含所有非聚合字段;如果要查整行记录,不能只GROUP BY target_column,否则其他字段值不确定(MySQL 5.7+ 默认 SQL mode 下会报错) -
HAVING COUNT(*) > N中的N是阈值,比如找重复 3 次及以上的,写>= 3或> 2都可以,但语义要统一 - 若字段允许
NULL,GROUP BY会把所有NULL归为一组——这通常符合预期,但需确认业务是否真要把空值算作“重复”
查出所有重复的完整记录(不止分组结果)
仅靠 GROUP BY 只能拿到分组后的聚合结果,没法返回原表中每一条重复的行。这时候得用自连接或 IN 子查询。
- 推荐写法:
SELECT * FROM t1 WHERE target_column IN ( SELECT target_column FROM t1 GROUP BY target_column HAVING COUNT(*) > N );
注意:如果target_column有NULL,IN会忽略它,查不到对应记录;改用EXISTS或IS NULL单独处理更稳妥 - 自连接方式性能较差,尤其大表时容易慢:
SELECT DISTINCT a.* FROM t1 a JOIN t1 b ON a.target_column = b.target_column GROUP BY a.id, a.target_column HAVING COUNT(*) > N——不建议,除非要关联统计上下文 - MySQL 8.0+ 可用窗口函数,但要注意:若用
COUNT(*) OVER (PARTITION BY target_column),必须配合派生表或 CTE 才能过滤,否则WHERE无法直接引用窗口函数
性能和索引怎么配才不卡
重复查询本质是扫描+分组,没索引时全表扫描不可避免。关键在 GROUP BY 字段是否走索引。
- 单列重复检查:给
target_column加普通索引即可,INDEX (target_column) - 复合场景(比如按
status和user_id联合重复):建联合索引顺序很重要,把等值条件字段放前面,例如INDEX (status, user_id) - 如果经常查「重复且满足某条件」,比如
WHERE deleted = 0再统计重复,可以把条件字段纳入索引:INDEX (deleted, target_column) -
COUNT(*)在 InnoDB 下需要实际遍历行(不是直接读元数据),所以索引覆盖越全,扫描越少
常见错误:COUNT(*) 和 COUNT(字段) 混用
查重复次数时,几乎总是该用 COUNT(*),而不是 COUNT(target_column)。
-
COUNT(*)统计行数,包括target_column为NULL的行 -
COUNT(target_column)会跳过该字段为NULL的行——如果你的业务里空值也算一种有效状态,这就漏统计了 - 别信某些教程写的
COUNT(1)更快,MySQL 对COUNT(*)、COUNT(1)、COUNT(pk)优化程度基本一致,可读性上COUNT(*)最准确
实际写的时候,最容易被忽略的是 NULL 值行为和索引覆盖范围。特别是线上表数据量上去之后,没索引的 GROUP BY 可能直接拖垮整个查询。











