使用在线重定义重构亿级分区表

在我的印象中,一直以来都会收到一封报警邮件,之前分析过,排查过,最后发现是一个遗留问题,协调开发同学,停业务维护还是有一些难度,最后不了了之了,在今天又突然想起了这件事情,觉得还是需要做点什么。
报警邮件类似下面的形式:
ZABBIX-监控系统:
------------------------------------
报警内容: Disk I/O is overloaded on 10.127.2.134_xx机房_xxxx
------------------------------------
报警级别: PROBLEM
------------------------------------
监控项目: CPU iowait time:17.21 %
------------------------------------
报警时间:2016.09.26-04:05:33
这是一台备库11gR2的环境,在ADG模式下的数据变化也会单独采样,这一点非常难得。所以很看到备库的DB time情况,可以明显看到在早间的时候有很大的抖动。

而基于快照,定位到具体的语句情况下,可见SQL_ID(4rhpc838qfsmy)就是我们要攻坚的重点了。

这是什么样的一个语句呢,让我很感意外。语句竟然看起来很简单,而且看起来竟然有两个执行计划。
$ sh showsqltext.sh 4rhpc838qfsmy
select * from (select user_ip,end_time from bill_logout_cn where cn=:1 order by end_time desc) where rownum=1
而且仔细看语句还是有一点优化的味道。 这样一个语句怎么性能会很差呢。
通过awrsqrpt得到的结果如下:

对这样一个语句,存在两个执行计划,就很奇怪了。

第一个执行计划虽然是索引扫描,但是I/O等待很高。
 
 Plan 2(PHV: 2814547617)
-----------------------

第二个执行计划产生了大量的buffer gets,使用了全表扫描。


  这是一个什么类型的表呢,数据量有2亿多,CN字段存在一个非唯一性索引。
执行计划如下

这个语句一个是使用了index skip scan,瓶颈在于扫描了大量的分区,结果大量的IO等待都在于此。
而第二个执行计划索性走了全表扫描,可见还是在运行中根据CBO评估而得全表的代价要相对低一些。

对于这个问题有几个疑问,首先这个语句性能如此之差,为什么在主库没有报警而在备库呢,其实原因是这样,主库的配置信息要好很多,这些问题和负载在主库都不是问题,以至于这个问题的影响在主库被弱化了。
而为什么语句走索引全扫描,全表扫描呢。这个其实说来话长,我查看了表的结构信息发现,这个表存在大量的分区,每天会生成一个分区,结果在2014年的某一天开始突然就停止了分区的维护,结果导致分区数据现在全都堆积在了默认分区上,这样就会性能一个很奇怪的数据分布,绝大多数的数据都分布在一个分区上,而还有很多历史数据分布在更多以日期为单位的分区上。
如果了解了问题的原委,其实也可以理解数据库在处理这个问题时的艰辛。
而对于这个问题的改进,就是需要重构分区,摆在我面前的由几件事情。首先是需要和开发确认是否历史数据可以清理,这个经过讨论,大家都带着保守态度;第二个问题是分区的维护,需要添加最近的一些分区,这个是否可以给出维护时间,不过经过讨论,在了解了业务特征之后,其实也可以做一个折中,那就是使用在线重定义来完成,尽管这是一个亿级数据的大表,但是因为是统计系统,所以数据更新很少,而且基本都是在凌晨胡统一更新,其他时段主要是查询为主,这样来看这个问题使用在线重定义其实还蛮不错的,互相成就,也不用互相协调区停业务应用了。
第二个问题解决之后第一个问题就好办了,可以在确认之后再具体部署。
好了,来到了重点的内容,那就是亿级大表的在线重定义,虽然之前做了周密的测试,但是还是有一些期待和小紧张。
和开发同学约定了下午的时间来在线维护,留给我的时间也不到一个小时了,要生成近900多个额外的分区,这个工作量着实不小,我采用了如下的SQL来动态生成需要补充的分区。
select 'PARTITION
BILL_LOGOUT_CN_'||to_char((trunc(sysdate)-990+level),'yyyymmdd')||'
VALUES LESS THAN
(TO_DATE('||chr(39)||(trunc(sysdate)-990+level+1)||chr(39)||',
'||chr(39)||'YYYY-MM-DD HH24:MI:SS'||chr(39)||'))  '
    ||'TABLESPACE ACCSTAT_DATA ,' from dual connect by level<990;
生成的语句类似下面的形式:
PARTITION BILL_LOGOUT_CN_20160924 VALUES LESS THAN (TO_DATE('2016-09-25
00:00:00', 'YYYY-MM-DD HH24:MI:SS'))  TABLESPACE ACCSTAT_DATA , 
。。。
我创建了一个新表BILL_LOGOUT_CN_DEF来和BILL_LOGOUT_CN最后做数据字典信息的交换。
在线重定义的前几步都是套路,因为没有主键,所以我使用rowid的方式。
exec  DBMS_REDEFINITION.CAN_REDEF_TABLE('TEST','BILL_LOGOUT_CN',2);  
exec  DBMS_REDEFINITION.START_REDEF_TABLE('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF',NULL,2); 
第二步运行之后,后台就开始忙碌起来了。
可以看到有会话在运行这样的语句。
SQL_FULLTEXT
----------------------------------------------------------------------------------------------------
INSERT /*+ BYPASS_RECURSIVE_CHECK APPEND SKIP_UNQ_UNUSABLE_IDX */ INTO
"TEST"."BILL_LOGOUT_CN_DEF"(M_ROW$$,"CN",XXXX) SELECT XXXX  FROM
"TEST"."BILL_LOGOUT_CN" "BILL_LOGOUT_CN"
                                                                  
而查看物化视图相关的数据字典,可以赫然看到有一个prebuilt物化视图,采用快速刷新的方式。

而在数据刷新之后,可以看到后台对于rowid的方式采用了下面的处理方式,即创建一个唯一性索引
SQL_FULLTEXT
----------------------------------------------------------------------------------------------------
 CREATE UNIQUE INDEX "TEST"."I_SNAP$_BILL_LOGOUT_CN_DEF" ON "TEST"."BILL_LOGOUT_CN_DEF" ("M_ROW$$")
  PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS
  TABLESPACE "TEST_DATA"
而这个索引也在不断增大。
SEGMENT_NAME                      SIZE_M     BLOCKS
------------------------------ --------- ----------
I_SNAP$_BILL_LOGOUT_CN_DEF          8065    1032320
                               ---------
sum                                 8065
好了,有的同学可能会说,在线重定义了解那么多干嘛,够用就行了。
没过多久,就看到数据复制的过程抛错了。
SQL> exec  DBMS_REDEFINITION.START_REDEF_TABLE('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF',NULL,2);  
BEGIN DBMS_REDEFINITION.START_REDEF_TABLE('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF',NULL,2); END;

*
ERROR at line 1:
ORA-14010: this physical attribute may not be specified for an index partition
ORA-06512: at "SYS.DBMS_REDEFINITION", line 56
ORA-06512: at "SYS.DBMS_REDEFINITION", line 1490
ORA-06512: at line 1
这个问题如果查看metalink,博客可能还没有很针对性的解答,这个就需要对在线重定义的过程很熟悉。
简单分析发现就是在表空间上出了问题。
重新分配扩展空间之后,再次开启重定义,会抛出下面的错误。
SQL> exec  DBMS_REDEFINITION.START_REDEF_TABLE('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF',NULL,2);  
BEGIN DBMS_REDEFINITION.START_REDEF_TABLE('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF',NULL,2); END;

*
ERROR at line 1:
ORA-23539: table "TEST"."BILL_LOGOUT_CN" currently being redefined
ORA-06512: at "SYS.DBMS_REDEFINITION", line 56
ORA-06512: at "SYS.DBMS_REDEFINITION", line 1490
ORA-06512: at line 1
为了加快处理过程,我们也可以手工处理一部分
删除对应的物化视图和物化视图日志
SQL> drop materialized view test.BILL_LOGOUT_CN_DEF;
Materialized view dropped.
SQL> drop materialized view log on test.BILL_LOGOUT_CN;
Materialized view log dropped.
看来这个过程可以完全证明在线重定义是使用物化视图快速刷新的。
来终止一下重定义过程,重新来过。其实这个步骤就在做truncate的操作。
execute dbms_redefinition.ABORT_REDEF_TABLE ('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF');
重新开始数据复制,重定义。
SQL> exec  DBMS_REDEFINITION.CAN_REDEF_TABLE('TEST','BILL_LOGOUT_CN',2);
SQL>exec  DBMS_REDEFINITION.START_REDEF_TABLE('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF',NULL,2);  
这一次就顺利多了,一次搞定。
最后完成前可以再手工刷新一下增量数据,保持数据的gap尽可能小。
SQL> execute dbms_redefinition.sync_interim_table ('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF');
PL/SQL procedure successfully completed.
最后交换数据字典信息即可完成。
SQL> exec  DBMS_REDEFINITION.FINISH_REDEF_TABLE('TEST','BILL_LOGOUT_CN','BILL_LOGOUT_CN_DEF');
PL/SQL procedure successfully completed.
Elapsed: 00:00:39.82
解决了之个问题之后,后续还有一些小的地方需要注意,补充新的分区,持续观察性能改进,历史分区数据的清理等。

时间: 2024-07-30 11:01:02

使用在线重定义重构亿级分区表的相关文章

在线重定义分区表和NOLOGGING APPEND分区表对比

1.模拟大表create table tes_p(it int, ic int, im varchar2(20)); declare     i number(10);begin   for i in 1..1000000  loop  insert into tes_p   values(i,i,'test');  end loop;end;commit;建立主键alter table tes_p add constraint pk_t primary key (it);方法1:在线重定义建立

Oracle 表在线重定义之普通表到分区表

对于一个7*24的在线生产系统来说,修改表定义(DDL)时一件非常痛苦的时,因为如果直接使用 alter语句,将会在表上安放一个排他锁,也就是说在这期间所有的DML和select都无法操作,如果是一 个大表,alter的时间将很长,在这期间应用会受到很大的影响. 幸好,从9i开始,Oracle提供了在线表重定义功能,在修改表定义的同时几乎不影响DML和select语 句,因为排他锁只会在表上出现很短时间.但是在线表重定义需要额外的空间(大致等于原表空间大小 ). 在线表重定义具有如下功能: 修改

【Oracle】利用在线重定义的方式改变普通表为分区表

将普通表改为分区表有如下几种方式: 1 创建一个和原表一样的分区表A_NEW ;    将insert A_NEW SELECT * FROM A;    将表A 命名为A_OLD 将A_NEW 该名为A; 2 利用在先重定义的方式!也是接下来要介绍的方法! 第一种方式需要停止应用对A的写访问;使用在线重定义的方式可以对应用透明! 测试例子如下:1 创建测试表 创建普通表: @bigtab.sql --tom 的大表创建脚本! 创建中间分区PART_TAB,使用PART_TAB来替换bigtab

关于分区表的在线重定义

目前项目中有一个问题,存在一个分区表,因为分区规则的问题,使得分区表中的数据分布很不均匀,数据都分区在了默认的maxvalue分区上.现在需要重新划分分区.从常规的角度来看,这中重新分区的问题一般有以下几个步骤. 1.数据备份,采用exp/expdp或者ctas的方式 2.truncate 分区表的数据 3.删除多余的分区,只保留maxvalue的分区 4.使用split partition进行分区 5.使用开始的数据备份,把数据导入. 对于这个问题,如果采用这种方式,势必会需要一定的downt

在线重定义的补充测试

    在很多时候,我们都是需要保持业务的可持续性,尽管说DDL的过程持续时间很短,但是在线业务出现,就会阻塞DML,导致业务访问中断,事务收到影响,所以在有些场景下,高可用的需求可能比性能的需求优先级还要高一些.     比如一个分区表,突然发现分区的规则存在一些问题,如果需要重新规划分区,部署,可能对于在线业务影响较大,能不能平滑的过渡到重新规划的分区模式下.     比如一个普通表,随着数据量的增加发现已经存在一些管理瓶颈,比如历史数据的清理比较麻烦,想改为分区表的方式     比如一个表

Oracle在线重定义失败后的处理

普通表在线重定义为分区表过程中报错,数值范围超过了分区限制大小,那么想要重新对表进行在线重定义需要经过哪些步骤呢?这个例子记录了处理过程: SALES@ORCL>exec dbms_redefinition.start_redef_table('SALES', 'SALES', 'SALES_P'); BEGIN dbms_redefinition.start_redef_table('SALES', 'SALES', 'SALES_P'); END; * ERROR at line 1: OR

oracle 11g的在线重定义简介

在Oracle9i出现之前,你只能通过MOVE或导出和导入的方式来进行表的重定义,因此表重定义的过程可能相当漫长或者说是一个离线过程,在此期间应用程序对该表的操作将失败.除了这个,如果用exp,我们也不能保证exp的时候该表的数据没有改变(除非单用户),而imp更是一个漫长的过程.为了解决这个问题,Oracle9i在其DBMS_REDEFINITION软件包中引入了在线重定义功能.这个特性对24*7的数据库系统来说非常重要,使用这个技术DBA可以在保持表允许DML语句的情况下修改结构,比如添加列

【Oracle】在线重定义表

Oracle从9i版本开始提供了在线重定义表功能,通过调用DBMS_REDEFINITION包,可以在修改表结构的同时允许DML操作. 在线重定义表具有以下功能: 1 修改表的存储参数 2 可以将表转移到其他表空间 3 增加并行查询选项 4 增加或删除分区 5 重建表以减少碎片 6 将堆表改为索引组织表或相反的操作 7 增加或删除一个列 调用DBMS_REDEFINITION包需要EXECUTE_CATALOG_ROLE角色和以下权限: CREATE ANY TABLE ALTER ANY TA

基于 dbms_redefinition 在线重定义表

      Oracle 支持在线重定义表,也就是说我们可以在修改表结构(DDL)的同时进行相关的DQL.DML操作,使得前端的DML根本感觉不到表结构实际上已经发生了变化,对于用户而言是完全透明的.当然在线重定义期间,前端性能会稍微有所下降.Oracle提供的重定义包dbms_redefinition即是用与完成此操作.其实质是Oracle使用了智能物化视图及物化视图日志的方式.在对象结构重组期间,表现为一个本地对象的复制,重组期间发生的任何变化都会被刷新到最新.   1.在线重定义表的主要功