1.1. 关键字:联机重定义/SYNC_INTERIM_TABLE/GATHER_TABLE_STATS
1.2. 需求:数据表的清理机制需要优化
离线消息表采用delete的方式定期对过期的数据进行清理,在数据库检测日志中发现每次清理时间都特别长,且清理过程中数据库服务器IO和CPU使用率超高,希望对清理机制进行优化。
1.3. 分析:数据量大
1.涉及到的表有50张,从OFFMSG_0到OFFMSG_49,数据量比较大,最少的一张有一千多万数据,35G左右,最大的一张有几亿数据,超过300G
2.这些表使用比较频繁,超过8000万用户使用,在凌晨两三点也一直有数据进出,不能停机
3.现有的表清理机制是每天凌晨1点开始清理,delete掉日期在7天的数据
综合以上分析,可以将这些表进行分区,每天的数据写入到一个分区,清理时每天truncate掉对应的分区即可
4.实际测试,一张1700万数据的表(35G)进行联机重定义,需要20分钟
5.根据测试的结果估算,平均每张表需要执行超过30分钟,所有表执行完耗时超过25小时
6.运维可接受的一次执行脚本时间在4小时以内
7.要分区的表有正在执行的清理策略,当分区后,清理策略需要进行调整
综合以上情况分析,最终方案确定为:分成8个批次进行联机重定义,将每个联机重定义过的表应用新的清理策略,没有联机重定义的表还使用原有的清理策略
1.4. 操作
检查表空间
联机重定义过程中所需的表空间大小相当于当前已使用的表空间大小,也就是说要联机重定义的表所在的表空间至少要有一半的空余表空间、索引空间和undo空间。
这几个空间保证很关键,有几次我在执行的时候都是只关注了表空间,没有留意索引空间和undo空间,造成索引空间和undo空间被打爆了,联机重定义失败。
检查可否联机重定义
以DBA用户在数据库执行脚本
begin
DBMS_REDEFINITION.CAN_REDEF_TABLE('DBUSER',
'OFFLMG_0',
DBMS_REDEFINITION.CONS_USE_ROWID);
END;
/
出现’ PL/SQL procedure successfully completed.’,说明该表支持联机重定义。如果这个检查都没有通过的话,恭喜你,GG了。
在这里有一点要注意,我操作的这些表是没有主键的,所以使用了DBMS_REDEFINITION.CONS_USE_ROWID,当操作的表有主键时,应该使用DBMS_REDEFINITION.CONS_USE_PK。
Shell脚本
由于需要批量执行,我们将联机重定义脚本设计成shell脚本,在执行时通过参数指定要做哪些表。如下:
#!/bin/ksh
rm -f nohup.out
ORACLE_SID=DBSID;export ORACLE_SID
nohup sqlplus "/as sysdba" << EOF
set serveroutput on;
declare
V_SIGN NUMBER(10);
V_MAX_SIGN NUMBER(10);
V_CREATE_TABLE VARCHAR2(8000);
... ...
begin
V_SIGN := $1;
V_MAX_SIGN := $2;
WHILE V_SIGN <= V_MAX_SIGN LOOP
... ...
这样就可以通过./ShellName 5 8 这种方式指定每次要执行的表了。
创建中间表
在脚本中首先要创建中间表,如:
V_CREATE_TABLE :='CREATE TABLE OFFLMG_0_MY
( ID NUMBER(22,0) NOT NULL,
... ...
--STEP1:定义分区标示字段
PARTITIONFLAG NUMBER(8,0)
)
--STEP2:使用list分区方式
PARTITION BY LIST (PARTITIONFLAG)
(
--STEP3:定义第一个分区
PARTITION P_OFFLMG_0 VALUES (0) TABLESPACE IMLOG,
PARTITION P_OFFLMG_1 VALUES (1) TABLESPACE IMLOG,
... ...
--STEP4:定义默认分区
PARTITION P_OFFLMG_100 VALUES (default) TABLESPACE IMLOG
)
TABLESPACE IMLOG ';
EXECUTE IMMEDIATE V_CREATE_TABLE;
说明:
STEP1:中间表需要有一个分区标示字段,该字段的值作为分区的依据,该字段一般是NUMBER类型的
STEP3:将0值划分到分区P_OFFLMG_0分区,也可以用VALUES(0,1,2,3)的方式将若干个值划分到同一分区,也可以用VALUES LESS THAN(100)的方式将小于100的值划分到同一分区。
例:
PARTITION P_0 VALUES LESS THAN(100) ,
PARTITION P_1 VALUES LESS THAN(200) ,
这段脚本定义了P_0分区存放分区标示字段小于100的数据,P_1分区存放分区标示字段大于等于100小于200的数据。
STEP4:使用list分区时要注意,当你加上default分区后,以后需要添加分区时不能直接add,需要先drop掉default分区再add(会弄丢default分区的数据),或者从default分区split出新分区(不会弄丢default分区的数据,但是当default分区数据比较多时,split操作会特别慢)。
联机重定义
--STEP1:将中间表和要操作的表进行关联
DBMS_REDEFINITION.START_REDEF_TABLE('OFFLMG_0','OFFLMG_0_MY',
'ID ID,TO,......,PARTITIONFLAG PARTITIONFLAG',DBMS_REDEFINITION.CONS_USE_ROWID);
--STEP2:开始进行数据同步
DBMS_REDEFINITION.SYNC_INTERIM_TABLE('OFFLMG_0','OFFLMG_0_MY');
--STEP3:并行创建索引
V_CREATE_INDEX_A :='CREATE INDEX OFFLMG_0_MY_ID ON OFFLMG_0_MY (ID) LOCAL TABLESPACE IMIDX PARALLEL 16';
EXECUTE IMMEDIATE V_CREATE_INDEX_A;
--STEP4:将索引改为非并行的
V_ALTER_INDEX_A := 'ALTER INDEX OOFFLMG_0_MY_ID NOPARALLEL';
EXECUTE IMMEDIATE V_ALTER_INDEX_A;
--STEP5:进行数据整理、统计 DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'DBUSER',TABNAME=>'OFFLMG_0_MY',ESTIMATE_PERCENT=> 0.1,DEGREE=>4,CASCADE=>TRUE);
--STEP6:联机重定义结束前再次同步数据
DBMS_REDEFINITION.SYNC_INTERIM_TABLE('OFFLMG_0','OFFLMG_0_MY');
--STEP7:联机重定义结束
DBMS_REDEFINITION.FINISH_REDEF_TABLE('OFFLMG_0','OFFLMG_0_MY');
DBMD_OUTPUT.PUT_LINE('Table OFFLMG_0 Redefined !!!');
说明:
STEP3:要操作的表比较大,创建索引时可以用并行创建的方式进行,大大减少索引创建时间,创建索引时要创建成本地索引。当索引创建完成后,要将索引改成非并行的。
STEP5:这个比较重要,新创建的表同步过来的数据没有生成统计信息,要手动整理一下,这样就可以正常使用了。
STEP6:联机重定义结束前一定要再次同步数据,因为整个操作是不停机执行的,在创建索引和整理数据的时候表中难免有新的数据进来,这个时候执行下同步就能保证新进来的数据也完整同步了。
错误处理
当表空间不足或其他原因造成联机重定义操作异常终止时,需要手动进行回退。脚本如下:
DBMS_REDEFINITION.ABORT_REDEF_TABLE ('OFFLMG_0','OFFLMG_0_MY');
说明:
这个时候只删除中间表是删除不掉的,因为联机重定义会创建实例化视图和实例化视图日志。需要先删除实例化视图日志,再删除实例化视图,最后才能删除中间表。
尾声
当联机重定义结束后, 需要确认数据完全一致,且数据表使用正常后才能删除掉中间表。