HAVING COUNT(1) > 1仅筛选重复分组,不直接判定脏数据;是否脏需结合分组字段业务含义、唯一性要求及行内容一致性综合判断。

HAVING COUNT(1) > 1 本身不排查脏数据,它只暴露重复分组
直接写 HAVING COUNT(1) > 1 不会“排查脏数据”,只会把 GROUP BY 后出现多次的分组挑出来——而这些分组是否真脏,得看你的分组依据和业务逻辑。比如按 email 分组后发现某邮箱出现 5 次,这可能是恶意注册、爬虫灌入,也可能是用户反复重试导致的合法冗余;但若按 order_id 分组还出现 COUNT(1) > 1,那基本就是双写或主键失效,属于明确脏数据。
必须先明确「重复」是否等于「脏」
用 HAVING COUNT(1) > 1 前,得回答三个问题:
- 你按什么字段(或字段组合)分组?是业务唯一键(如
user_id、email),还是技术主键(如id)?按id分组永远只有 1 条,HAVING无意义 - 该字段是否本应唯一?比如订单表的
order_no理论上不能重复,但日志表的event_type重复千次也正常 - 重复行内容是否完全一致?如果
nick_name相同但created_at差几秒,可能是并发插入;如果所有字段都一样,大概率是上游没做幂等
错误示例:SELECT email FROM users GROUP BY email HAVING COUNT(1) > 1 —— 这只能告诉你哪些邮箱重复了,但无法区分是“一人多注”还是“系统 bug”。要判断,得补全字段:SELECT email, COUNT(1), MIN(created_at), MAX(updated_at) FROM users GROUP BY email HAVING COUNT(1) > 1。
NULL 值会让 HAVING 结果失真,必须显式处理
GROUP BY 会把所有 NULL 归为同一组,哪怕它们语义完全不同。比如 phone 字段大量为 NULL,执行 GROUP BY phone HAVING COUNT(1) > 1 会返回一条 “NULL, 12000”,但这不是脏数据线索,只是空值泛滥的表象。
- 想排除 NULL 干扰:加
WHERE phone IS NOT NULL预过滤 - 想单独检查 NULL 是否异常:用
GROUP BY phone IS NULL,再HAVING COUNT(1) > 1看空值占比是否突增 - 字符串字段记得
TRIM():否则' '和''会被分到不同组,漏掉“假非空”脏数据
性能陷阱:没索引的 GROUP BY + HAVING 可能卡死
在千万级表上对无索引字段(如 address)跑 GROUP BY address HAVING COUNT(1) > 1,MySQL 很可能触发 Using temporary; Using filesort,查几分钟不出结果。
- 先用
EXPLAIN看执行计划,确认type是ref或range,不是ALL - 高频排查字段(如
email、mobile)务必建索引:ALTER TABLE users ADD INDEX idx_email (email) - 大表慎用
HAVING COUNT(1) > 1全量扫,可改用采样:LIMIT 10000加子查询控制范围
真正危险的不是重复本身,而是你默认“重复 = 脏”,却没验证分组键是否真能代表业务实体。比如按 name 分组发现重复,但“张伟”在全国有几十万人——这时候 HAVING 报出的不是脏数据,是统计常识。










