DISTINCT仅查询去重,不删除原表重复数据;删重应使用ROW_NUMBER()窗口函数(MySQL 8.0+)、自连接(5.7及以下)或换表法,并配索引、备份与唯一约束。

用 DISTINCT 只是查重,不是删重
很多人一看到“去重”就写 SELECT DISTINCT,结果发现表里重复数据还在——因为 DISTINCT 只影响查询结果,不碰原表。它适合统计、看样例、导出报表,不适合清理脏数据。
常见错误现象:SELECT DISTINCT name, email FROM user_info 返回 100 行,但 SELECT COUNT(*) FROM user_info 还是 500 行,误以为“已经去重成功”。
-
DISTINCT作用于整行 SELECT 的字段组合:只有所有字段值完全一致才算重复 - 如果选了
id(比如SELECT DISTINCT id, name, email),基本不会去重——因为id天然唯一 - 性能上,
DISTINCT本质是隐式GROUP BY,大数据量时可能触发临时表和文件排序,变慢
删重复记录,优先用 ROW_NUMBER() 窗口函数(MySQL 8.0+)
这是目前最清晰、可控、可读性最强的删除方式,尤其当你需要“按时间保留最新一条”或“按 ID 保留最小一条”时。
实操建议:
- 先确认 MySQL 版本:
SELECT VERSION();——低于 8.0 就别硬套,会报错This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' - 用 CTE +
ROW_NUMBER()安全定位要删的行:WITH dupes AS ( SELECT id, ROW_NUMBER() OVER (PARTITION BY name, email ORDER BY id) AS rn FROM user_info ) DELETE FROM user_info WHERE id IN (SELECT id FROM dupes WHERE rn > 1);
-
PARTITION BY name, email定义“哪些字段相同算重复”,ORDER BY id决定哪条留下(升序留最小 ID,降序留最大 ID)
MySQL 5.7 或更低版本?用自连接删除更稳
老版本不支持窗口函数,也不允许 DELETE 中直接子查询引用自身表,NOT IN 套两层 SELECT 虽能绕过,但容易漏删(NULL 导致逻辑失效)、性能差、难调试。
推荐做法是自连接,语义直白,引擎优化成熟:
- 语法简洁:
DELETE e1 FROM user_info e1 INNER JOIN user_info e2 WHERE e1.id > e2.id AND e1.name = e2.name AND e1.email = e2.email;
- 它表示:“只要存在另一条 name+email 相同、且 ID 更小的记录,就把当前这条删掉”——天然保留最小 ID
- 务必确保
(name, email)上有索引,否则多表扫描极慢;没索引先建:CREATE INDEX idx_name_email ON user_info(name, email); - 执行前一定先备份,或在事务里测试:
BEGIN; DELETE ... ; SELECT ROW_COUNT(); ROLLBACK;
大批量数据清理,别在原表上硬刚
几百万行以上删重复,无论用哪种 SQL,都可能锁表久、日志暴涨、主从延迟飙升——生产环境风险极高。
稳妥做法是“换表法”,本质是重建:
- 创建结构一致的新表:
CREATE TABLE user_info_clean LIKE user_info; - 导入去重后数据(注意:
GROUP BY配合MIN(id)等聚合取唯一行):INSERT INTO user_info_clean SELECT MIN(id) AS id, name, email, other_cols FROM user_info GROUP BY name, email;
- 原子替换:
RENAME TABLE user_info TO user_info_bak, user_info_clean TO user_info; - 换完立刻加唯一约束防复发:
ALTER TABLE user_info ADD UNIQUE KEY uk_name_email (name, email);
真正容易被忽略的是:换表后外键、触发器、权限、监控指标这些配套项是否同步更新——它们不会自动迁移。










