用 GROUP BY + MAX() 先定位每组重复数据中要保留的最大 id,再通过 JOIN 安全删除其余重复行;注意 HAVING COUNT(*) > 1 过滤单条记录,NULL 值需单独处理。

用 GROUP BY + MAX() 找出每组重复数据中最大的 id
核心思路是:先按业务字段(比如 email、phone)分组,再用 MAX(id) 拿到每组里要保留的那条记录的 id。这步不删数据,只是定位目标。
假设表叫 users,重复依据是 email 字段:
SELECT MAX(id) AS keep_id, email FROM users GROUP BY email HAVING COUNT(*) > 1;
注意 HAVING COUNT(*) > 1 是必须的——它过滤掉那些其实不重复的组,避免误选单条记录的 id。
用 JOIN 或子查询删除其余重复行
直接 DELETE FROM users WHERE id NOT IN (...) 很危险:MySQL 不允许对同一张表先查后删(会报错 You can't specify target table 'users' for update in FROM clause)。得绕开。
推荐用 JOIN 写法,安全且高效:
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
- 先用
SELECT模拟要删的数据:SELECT * FROM users u1 INNER JOIN users u2 ON u1.email = u2.email AND u1.id
- 确认无误后,再跑
DELETE,并加上LIMIT控制单次操作量(尤其大表):DELETE u1 FROM users u1 INNER JOIN users u2 ON u1.email = u2.email AND u1.id
- 如果表有主键以外的唯一索引(比如
UNIQUE(email)),删完记得检查是否冲突——因为删除过程可能让原本被覆盖的重复值“浮上来”。
事务 + 备份是底线,不是可选项
哪怕测试环境也别裸删。真实场景下,DELETE 语句一旦执行,binlog 虽能恢复,但中间状态不可逆。最稳妥做法:
开启事务:
BEGIN; DELETE u1 FROM users u1 INNER JOIN users u2 ON u1.email = u2.email AND u1.id 1; -- 没问题再提交 COMMIT;
如果发现删错了,立刻 ROLLBACK。另外,执行前用 mysqldump 导出关键数据,比任何补救都快。
真正麻烦的不是语法,而是没意识到:重复逻辑是否覆盖了所有业务场景(比如 NULL 值在 GROUP BY 中会被归为一组,但 JOIN 条件里 u1.email = u2.email 会跳过 NULL)——这点常被忽略。










