根本原因是未明确“删哪张表”和“依据什么判重”,导致MySQL报ERROR 1093、PostgreSQL报table name重复;必须用别名隔离自引用,GROUP BY字段需匹配业务判重逻辑并处理NULL,删后须执行OPTIMIZE TABLE或VACUUM回收空间。

DELETE + JOIN 删重复时,为什么总删错或报错
根本原因不是语法写不对,而是没分清“删哪张表”和“依据什么判重”。MySQL 5.7 报 ERROR 1093,PostgreSQL 报 table name "t" specified more than once,本质都是在同一个语句里对同一张表既查又删,且没加别名隔离。
常见翻车点:
- 写
DELETE FROM t WHERE id NOT IN (SELECT MIN(id) FROM t GROUP BY x)—— MySQL 直接拒绝执行 - PostgreSQL 里漏写子查询别名,比如
USING (SELECT MIN(id) FROM t GROUP BY x)没起名,就报错 - 判重字段含
NULL,GROUP BY x把所有NULL归为一组,但=判断不成立,结果要么全删、要么全不删
MySQL 8.0+ 安全删重复:用 JOIN 代替子查询
推荐写法是把保留逻辑转成连接条件,绕过 ERROR 1093,也避免临时表开销。
DELETE a FROM users a LEFT JOIN (SELECT MIN(id) AS keep_id, email FROM users GROUP BY email) b ON a.id = b.keep_id WHERE b.keep_id IS NULL;
要点:
-
a是主删表,b是派生表,必须显式起别名 -
GROUP BY字段(如email)必须和业务判重逻辑一致;联合判重就写GROUP BY email, status - 如果
email允许为NULL,得额外加WHERE email IS NOT NULL过滤,否则NULL全被归进同一组 - 删前先跑
SELECT * FROM users a LEFT JOIN (...) b ON ... WHERE b.keep_id IS NULL LIMIT 10;看实际要删哪些行
PostgreSQL 删除重复:必须用 USING + 别名
PostgreSQL 不支持 DELETE t FROM t JOIN ...,只能走 USING 语法,且子查询必须有别名。
DELETE FROM users u USING (SELECT MIN(id) AS keep_id, email FROM users GROUP BY email) keep WHERE u.email = keep.email AND u.id != keep.keep_id;
关键细节:
-
USING后的子查询必须起别名(这里是keep),否则报错 -
WHERE条件里必须同时匹配判重字段(u.email = keep.email)和排除主键(u.id != keep.keep_id),缺一不可 - 同样要处理
NULL:若email可空,得在子查询里加WHERE email IS NOT NULL,否则GROUP BY email会把所有NULL当作一组,只留一个,其余全删 - 大数据量时,给
email加索引能明显加速GROUP BY和JOIN
删完别以为万事大吉:空间不释放,后续查询还变慢
DELETE 只做标记删除,磁盘空间不会自动回收,MVCC 版本链越积越长,buffer pool 命中率下降,连带拖慢其他查询。
必须补操作:
- MySQL InnoDB:
OPTIMIZE TABLE users(小表),或ALTER TABLE users ENGINE=InnoDB(中大表,更可控) - PostgreSQL:
VACUUM users(日常清理死亡元组),真要缩文件大小才用VACUUM FULL users(锁表,避开高峰期) - 执行前查下表大小:
SELECT table_name, data_length FROM information_schema.tables WHERE table_schema='your_db' AND table_name='users';,删完再比一次,确认空间是否真正释放
最常被跳过的一步,就是没验证删后空间是否回收——看起来数据少了,其实磁盘和查询性能都没改善。

















