mysql无内置rowid,所谓“rowid法”实为用min(id)/max(id)模拟;完全重复行去重首选临时表法(create table as select distinct + truncate + insert),有主键时可用left join保留指定记录。

MySQL 没有内置 ROWID,直接套用 Oracle 的 rowid 语法会报错(Unknown column 'rowid' in 'where clause');对完全重复行去重,临时表法最稳,但必须注意主键/自增字段的处理。
MySQL 里根本不存在标准 ROWID
Oracle 中的 rowid 是物理地址标识符,MySQL 不提供等价机制。你看到的所谓 “ROWID 法” 实际是误传或混淆——部分文章把 MIN(id) 或 MAX(id) 当作逻辑 ROWID 用,但这是靠业务主键模拟,不是真正意义上的行地址。强行写 WHERE rowid NOT IN (...) 会直接报错,且无法在无主键表中使用。
- 检查是否真有
rowid字段:DESC your_table,如果没这一列,就别碰它 - 即使表有
id字段,也得确认它是PRIMARY KEY或NOT NULL UNIQUE,否则MIN(id)不能代表“第一行” - 无主键、全字段重复的表,
rowid思路彻底失效,只能走临时表 +DISTINCT
临时表法:适用于完全重复行(所有字段都相同)
这是 MySQL 最可靠、兼容性最好的方式,尤其适合没有主键或主键不参与去重逻辑的场景。核心是用 SELECT DISTINCT * 抽出唯一行,再重建原表。
- 先建临时表:
CREATE TABLE tmp_table AS SELECT DISTINCT * FROM original_table - 清空原表:
TRUNCATE TABLE original_table(比DELETE快,且重置自增计数器) - 导回数据:
INSERT INTO original_table SELECT * FROM tmp_table - 删临时表:
DROP TABLE tmp_table
⚠️ 注意:TRUNCATE 会丢失自增 ID 连续性;若需保留原 ID 序列,改用 DELETE + 重建自增索引,但性能更差。
LEFT JOIN 删除法:适用于有主键且按字段组合去重
当你要根据某些字段(如 email, phone)判定重复、并保留其中一条(比如 id 最大的),LEFT JOIN 比子查询高效得多,避免了 NOT IN 对 NULL 的坑和全表扫描。
- 示例(保留每组
email中id最大的记录):DELETE t1 FROM users t1 LEFT JOIN ( SELECT MAX(id) AS max_id, email FROM users GROUP BY email ) t2 ON t1.id = t2.max_id AND t1.email = t2.email WHERE t2.max_id IS NULL;
- 关键点:
ON条件必须同时匹配分组字段和主键,否则关联失效 - 不支持无主键表;若用
MIN(id),记得把=改成并调整 <code>IS NULL逻辑
为什么不能直接用 HAVING COUNT(*) > 1 删除?
HAVING 只能用于 SELECT 或 GROUP BY 查询结果过滤,MySQL 不允许在 DELETE 语句中直接嵌套带 HAVING 的子查询(报错 You can't specify target table for update in FROM clause)。所有想“先查再删”的尝试,都必须绕过这个限制——要么用临时表,要么用 JOIN,要么用变量模拟。
真正容易被忽略的是:去重前务必备份。千万级表执行 TRUNCATE 或 DELETE 后不可回滚(尤其禁用 autocommit 时没显式 BEGIN);而 DISTINCT 在含 TEXT / BLOB 字段的表上可能因排序缓冲区不足失败,得提前调大 sort_buffer_size。











