row_number()去重仅适用于mysql 8.0.2+,低版本需用group by+min(id)等替代;delete时须用子查询包装row_number()结果再join删除;partition by指定重复判断字段,order by决定保留哪行,且必须有索引和备份。

ROW_NUMBER() 去重只在 MySQL 8.0.2+ 可用
低于这个版本(比如 5.7 或 8.0.1)直接写 ROW_NUMBER() 会报错:FUNCTION xxx.ROW_NUMBER does not exist。先执行 SELECT VERSION(); 确认输出是类似 8.0.25 这样的字符串,再继续。如果版本不够,别硬套语法——得换方案,比如用 GROUP BY + MIN(id) 配合派生表,或者导出再导入。
DELETE 时不能直接嵌套 ROW_NUMBER() 查询
MySQL 不允许对同一张表在一条语句里既查又删,否则触发 ERROR 1093。必须把带 ROW_NUMBER() 的查询包装成子查询或 CTE,再 JOIN 或 WHERE 引用。
- 安全写法是:用子查询生成带
rn列的结果集,再和原表JOIN删除rn > 1的行 - 示例(按
email去重,保留id最小的):DELETE t1 FROM users t1 INNER JOIN ( SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users ) t2 ON t1.id = t2.id WHERE t2.rn > 1;
-
PARTITION BY后面填真正判断“重复”的字段,支持多列,如PARTITION BY user_id, status - 所有出现的
id要替换成你表里的实际主键或唯一标识字段
ORDER BY 决定哪条数据被保留,不是默认选第一条
ROW_NUMBER() 本身不决定留谁,ORDER BY 才是关键。它直接影响 rn = 1 是哪一行。
- 写
ORDER BY created_at DESC→ 保留最新插入的那条 - 写
ORDER BY id ASC→ 保留 ID 最小的那条 - 千万别省略
ORDER BY,也别指望“自然顺序”——MySQL 不保证物理插入顺序 - 如果有 NULL 值,
ORDER BY默认把 NULL 排最前(ASC)或最后(DESC),可能打乱预期;必要时显式加IS NOT NULL过滤
大表操作前必须备份,且注意索引影响
没索引的 PARTITION BY 字段会让 ROW_NUMBER() 扫全表,慢到超时。删除动作本身也会锁表、产生大量 binlog。
- 执行前先建备份:
CREATE TABLE users_backup AS SELECT * FROM users; - 检查
PARTITION BY字段是否有索引,没有就加:ALTER TABLE users ADD INDEX idx_email (email); - 如果表超千万行,别一次性删完;可分批,比如加
AND t1.id BETWEEN 10000 AND 20000控制范围 - 存储过程中用
ROW_NUMBER(),不能裸写在SET或INSERT INTO ... SELECT的子句里,必须包在子查询中
实际去重逻辑不难,但容易卡在版本判断、语法绕过、排序意图和性能盲区这四点上。尤其当业务要求“保留最新一条”,而字段又没索引、还混着 NULL 时,ORDER BY 和索引缺一不可。











