<p>最稳妥的去重删除方法是用派生表绕过MySQL 1093错误:DELETE FROM table WHERE id NOT IN (SELECT * FROM (SELECT MAX(id) FROM table GROUP BY column) AS tmp)。</p>

直接用 DELETE 配合 MAX(id) 或 MAX(时间字段) 是最常用也最稳妥的做法,但必须绕开 MySQL 的 1093 错误——它不许你一边查原表一边删原表。
为什么 DELETE ... WHERE id NOT IN (SELECT MIN(id) FROM table GROUP BY ...) 会报错?
因为 MySQL 在执行这条语句时,SELECT 和 DELETE 指向的是同一张表 table,引擎会拒绝这种“自引用更新”。错误信息是:1093 - You can't specify target table 'table' for update in FROM clause。
- 这不是语法错,是 MySQL 的设计限制(尤其在 InnoDB 下)
- 哪怕你加了别名、套了子查询,只要最内层
FROM还是原表名,就可能触发 - MySQL 8.0+ 对部分嵌套写法放宽了限制,但兼容性和可读性不如显式中间层
安全可靠的做法:用派生表(Derived Table)绕过 1093
核心思路是把分组结果先“固化”成一个临时结果集,再拿这个结果去过滤原表。关键就是多套一层 SELECT * 并给子查询起别名。
- 保留最新一条(按
id最大):DELETE FROM student WHERE id NOT IN (SELECT * FROM (SELECT MAX(id) FROM student GROUP BY name) AS tmp); - 保留最新一条(按时间字段,如
created_at):DELETE FROM order_log WHERE id NOT IN (SELECT * FROM (SELECT MAX(id) FROM order_log GROUP BY user_id, order_no) AS tmp); - 注意:
AS tmp不可省略,否则报1248 - Every derived table must have its own alias
多字段重复时怎么写?
GROUP BY 后面跟多个列即可,逻辑不变——只要组合值相同,就算重复。
- 例如按
email和status联合去重:DELETE FROM user WHERE id NOT IN (SELECT * FROM (SELECT MAX(id) FROM user GROUP BY email, status) AS tmp); - 如果字段含
NULL,注意GROUP BY会把所有NULL归为一组,可能误删;建议先用WHERE email IS NOT NULL AND status IS NOT NULL排除 - 大数据量时,确保
(email, status)有联合索引,否则GROUP BY会全表扫描
删除前务必验证,别跳过这步
直接跑 DELETE 很危险。先用 SELECT 看清哪些行会被删:
- 查出将被保留的 ID:
SELECT MAX(id) FROM student GROUP BY name; - 查出将被删除的记录(用于核对):
SELECT * FROM student WHERE id NOT IN (SELECT * FROM (SELECT MAX(id) FROM student GROUP BY name) AS tmp); - 如果表很大,加
LIMIT 10测试前几条,确认逻辑无误再删全量
真正执行时,建议包在事务里:BEGIN; DELETE ...; SELECT ROW_COUNT(); ROLLBACK; —— 先看影响行数,没问题再 COMMIT。别信“就删几百条没事”,线上环境没小事。


















