用group by+min(id)保留每组email中id最小的记录,再通过left join匹配删除其余重复行;需确保email字段有索引,mysql 5.7以下不支持row_number(),操作前须备份并验证。

用 GROUP BY + MIN(id) 找出要保留的记录
MySQL 没有直接的“去重删除”语法,得先明确保留哪一条:通常选 id 最小(或最大)的那条。假设表叫 users,重复依据是 email 字段,那么先查出每个 email 对应的最小 id:
SELECT MIN(id) AS keep_id, email FROM users GROUP BY email;
这个结果就是你“想留下的那些行”。下一步就是删掉其余的。
用 DELETE ... JOIN 一次性删掉重复行
这是最常用、效率也相对可控的方式。核心思路:把原表和刚才的分组结果做 LEFT JOIN,找出没被匹配上的那些行(即该 email 下 id 不是最小的),然后删掉它们:
采用 php+mysql 数据库方式运行的强大网上商店系统,执行效率高速度快,支持多语言,模板和代码分离,轻松创建属于自己的个性化用户界面 v3.5更新: 1).进一步静态化了活动商品. 2).提供了一些重要UFT-8转换文件 3).修复了除了网银在线支付其它支付显示错误的问题. 4).修改了LOGO广告管理,增加LOGO链接后主页LOGO路径错误的问题 5).修改了公告无法发布的问题,可能是打压
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条件里必须同时匹配email和id,否则会误删 -
WHERE u2.keep_id IS NULL表示这条u1记录没找到对应的“保留 ID”,该删
注意主键和索引对执行的影响
如果 email 字段没建索引,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范围),避免锁表太久
真正难的不是写出语句,而是确认“哪些字段才算重复”“保留逻辑是否符合业务”,比如两个 email 相同但 status 不同,该留激活的还是未激活的——这得看需求,SQL 本身不会替你判断。









