因为ROWID是Oracle每行物理地址的唯一标识,可安全删重;而DISTINCT和GROUP BY仅能查询去重,无法直接删除重复数据。

为什么用 ROWID 而不是 DISTINCT 或 GROUP BY?
因为 DISTINCT 和 GROUP BY 只能用于查询去重,不能直接删数据;而 ROWID 是 Oracle 每行物理地址的唯一标识,哪怕所有字段值完全一样,ROWID 也不同——这正是安全删重的底层依据。用它删重,不需要建索引、不依赖业务字段是否为空或是否可比较,也不怕 NULL 值干扰分组逻辑。
DELETE 语句中 ROWID != (SELECT MAX(ROWID) ...) 怎么写才不出错?
核心是子查询必须和外层表做正确关联,否则会误删整张表。常见错误是漏写 WHERE 关联条件,导致子查询返回单个最大 ROWID,然后外层把除了这一行以外的所有行全删掉。
- 正确写法必须包含等值关联:比如按
name和email判重,就要在子查询里写a.name = b.name AND a.email = b.email - 子查询必须用
MAX(ROWID)(保留最新插入的),不能用MIN(ROWID)(除非你明确要留最老的) - 外层
DELETE FROM 表名 a的别名a必须和子查询里的b区分开,且关联字段名前后一致 - 执行前务必先用
SELECT验证:SELECT a.* FROM 表名 a WHERE a.ROWID != (SELECT MAX(b.ROWID) FROM 表名 b WHERE a.name = b.name AND a.email = b.email)
大数据量下直接 DELETE 很慢甚至卡死?换临时表方案
当表有上百万行、重复率又高时,上面那个带子查询的 DELETE 会触发大量嵌套循环,执行计划容易走全表扫描 × 全表扫描,I/O 爆炸。这时应拆成两步:先建临时表存“该保留的 ROWID”,再用 NOT IN 或 ANTI JOIN 删除。
- 建临时表(只含判重字段 + 最大
ROWID):CREATE TABLE tmp_keep AS SELECT 字段1, 字段2, MAX(ROWID) keep_rid FROM 表名 GROUP BY 字段1, 字段2 - 删非保留行:
DELETE FROM 表名 a WHERE a.ROWID NOT IN (SELECT b.keep_rid FROM tmp_keep b WHERE a.字段1 = b.字段1 AND a.字段2 = b.字段2) - 注意:如果判重字段可能为
NULL,NOT IN会失效,改用NOT EXISTS更稳妥
删完不提交、没备份、没验证 —— 这三步最容易被跳过
ROWID 删重不可逆,Oracle 不支持 Flashback Query 回滚已提交的 DELETE(除非开了闪回区且时间窗口够长)。线上操作前必须:
- 在测试库用相同数据量跑通全流程
- 删之前用
CREATE TABLE 表名_bak AS SELECT * FROM 表名备份(比 expdp 快,适合紧急场景) - 删完立刻查:
SELECT 字段1, 字段2, COUNT(*) FROM 表名 GROUP BY 字段1, 字段2 HAVING COUNT(*) > 1,确认返回空结果 - 别忘了
COMMIT—— 有人写完DELETE就切走,事务挂太久锁表


















