前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >104-oracle大表删除重复记录的几种方法

104-oracle大表删除重复记录的几种方法

作者头像
老虎刘
发布2023-09-01 13:23:50
4990
发布2023-09-01 13:23:50
举报

表上某个字段(或某几个字段)有重复值,有需求要把重复记录删除,只保留一条.

如果是小表,随便怎么折腾都行; 如果是大表(至少1千万条记录以上,或者占用10G以上空间), 我们可能需要想办法加快这个速度 , 这时可以参考下面方法:

要求:

删除t1表 object_name字段上的重复记录,只保留其对应created字段最大的那一条.

先查表的总记录数和需要删除的重复记录数,dup_cnt就是需要删除的重复记录数:

代码语言:javascript
复制
--如果是多个字段去重,一起写到 group by 后面
select /*+ parallel(8) */
      sum(cnt) as total_cnt
     ,sum(cnt-1) as dup_cnt
from (select count(*) cnt from t1 group by object_name )
;

1.如果需要删除的重复记录比较少,比如几万条以下,可以用下面方法:

代码语言:javascript
复制
--如果是多个字段去重, 一起写到partition by 后面
delete 
   /*+ parallel(8) 
      opt_param('_hash_join_enabled' 'false') 
      opt_param('_optimizer_sortmerge_join_enabled' 'false') 
   */ 
from t1 
where rowid in 
(select rowid 
 from  
   (select rowid,row_number() over
          (partition by object_name 
           order by created desc nulls last) as rn 
    from t1) 
 where rn>1
);

加hint的目的是并行扫描大表,然后用Nested Loops(禁用了hash join和merge join,只能选择Nested Loops), 按rowid删除重复记录, 大表全表扫描只做一次. 不会锁表.

2.如果需要删除的重复记录比较多,比如几十万以上,可以用下面方法:

代码语言:javascript
复制
delete /*+ enable_parallel_dml   parallel(8) */
from t1 
where rowid in 
(select rowid from 
    (select rowid,row_number() 
         over(partition by object_name order by created desc nulls last) rn 
     from t1
    ) where rn>1
) ;

其中enable_parallel_dml 这个hint的作用是启用并行dml, 从12c开始支持. 如果没有这个hint,只是在表扫描时使用并行,delete不并行.

用这个hint会锁表, 直到commit或rollback才会释放锁. 如果不想锁表,可以去掉enable_parallel_dml hint.

这个方法对删除少量重复记录也是可用的.

注意:

如果表上索引比较多, 消耗时间会更长,如果业务允许,可以先禁用索引:

alter index xxx unusable;

删除操作完成后再重建索引:

alter index xxx rebuild online parallel;

注意:

需要删除的记录越多, 生成的redo和undo量就越大, 这种大事务的操作要慎重. 下面的方法3会把大事务拆分.

3.可以把大事务拆分, 比如拆分成10次:

把要删除记录的rowid保存到临时分区表, 然后逐个批次执行:

代码语言:javascript
复制
--创建临时表分区表保存待删除rowid及对应的批次:
--ntile(10) 分析函数负责把记录拆分等10份, 相邻rowid分在一组
--建分区表是为了避免临时表的多次全表扫描
CREATE TABLE tmp_t1_rid parallel 8
PARTITION BY RANGE (batch_id) interval (1)
(  
  PARTITION p1 VALUES LESS THAN (2)   
)as
select ntile(10) over (order by rowid) as batch_id 
        , rowid as rid
from  (select rowid,row_number() over
               (partition by object_name order by created desc nulls last) rn 
       from t1
       ) 
where rn>1;


--指定不同的batch_id=1..10, 分别执行, 共10次:

--batch_id=1
delete from t1 where rowid in 
(select rid from tmp_t1_rid where batch_id=1);
commit;

...batch_id=2~9
commit;

--batch_id=10
delete from t1 where rowid in 
(select rid from tmp_t1_rid where batch_id=10);
commit;

4.如果需要删除的重复记录非常多,可以考虑创建新表+改名的方法:

代码语言:javascript
复制
create table t_nodup parallel 8
as 
select * from --这个*要替换成字段列表,否则新表会多一个rn字段
(select a.*,row_number() over 
   (partition by object_name order by created desc nulls last) as rn 
  from t1 a
) 
where rn=1;

--然后改表名:
  rename t1 to t1_bak;
  rename t_nodup to t1;

最后还要把原表上的约束,索引,grant等相关信息应用在新表上.

5. 如果对保留记录没有要求(不要求保留对应最大created的那一条记录,只要不重即可),可以用下面简单写法(大表再把并行加上):

代码语言:javascript
复制
--并行相关hint可以酌情使用:
delete from t1 where rowid not in
(select max(rowid) from t1 group by object_name);

最后的建议:

为了避免生成新的重复记录, 建议在相关字段增加unique约束:

create unique index uidx_t1_object_name on t1(object_name) ;

补充:

rowid是oracle数据库独有的, mysql 和 postgresql 可以用主键字段代替, 思路差不多是相通的, 只不过语法有些差异,可以借鉴.

如有不妥之处,请指正,多谢!

(完)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老虎刘谈oracle性能优化 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档