用 GROUP BY
+ MIN(id)
找出要保留的记录
MySQL 没有直接的“去重删除”语法,得先明确保留哪一条:通常选
id最小(或最大)的那条。假设表叫
users,重复依据是
id:
SELECT MIN(id) AS keep_id, email FROM users GROUP BY email;
这个结果就是你“想留下的那些行”。下一步就是删掉其余的。
用 DELETE ... JOIN
一次性删掉重复行
这是最常用、效率也相对可控的方式。核心思路:把原表和刚才的分组结果做
LEFT JOIN,找出没被匹配上的那些行(即该
id不是最小的),然后删掉它们:
DELETE u1 FROM users u1
LEFT JOIN (
SELECT MIN(id) AS keep_id, email
FROM users
GROUP BY email
) u2 ON u1.email = u2.email AND u1.id = u2.keep_id
WHERE u2.keep_id IS NULL;
u1是原表别名,
u2是子查询结果别名
ON条件里必须同时匹配
id,否则会误删
WHERE u2.keep_id IS NULL表示这条
u1记录没找到对应的“保留 ID”,该删
注意主键和索引对执行的影响
如果
GROUP BY和
JOIN都会变慢,大表可能卡住甚至超时: 执行前加索引:
CREATE INDEX idx_email ON users(email);若表有自增
id主键,上面语句能用;但若没有主键或用复合唯一键,得改用
ROW_NUMBER()(仅 MySQL 8.0+ 支持) MySQL 5.7 及更早版本不支持窗口函数,别写
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id),会报错
ERROR 1064
误删风险高,务必先备份或用事务测试
这条
DELETE语句不可逆。线上操作前必须: 在测试库跑一遍,确认影响行数:
SELECT COUNT(*) FROM users u1 LEFT JOIN (...) u2 ... WHERE u2.keep_id IS NULL;用事务包住,删完立刻
SELECT验证:
START TRANSACTION; DELETE u1 FROM users u1 ... ; SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;如果表很大,考虑分批删(比如按
id范围),避免锁表太久
真正难的不是写出语句,而是确认“哪些字段才算重复”“保留逻辑是否符合业务”,比如两个
status不同,该留激活的还是未激活的——这得看需求,SQL 本身不会替你判断。
