I have developed a tool to make this process automatic. The concept of the tool is simple - I use a driver table that stores the preferences such as the partitioning time intervals of individual tables I am interested in and how often then should be purged. The actual tool is a stored procedure that reads this driver table, determines if the oldest partition satisfies the condition of purge and drops it if is it the case. You can put this procedure in a Scheduler job and run it every day. It will compute the partitions automatically is decide on dropping the relevant partitions everyday. It will also update global indexes.
Disclaimer: this is being provided for educational purpose only; use at your own risk. The author assumes absolutely no responsibility for any issues caused by the use of this tool.
First, I will need the driver table:
/* --------------------------------------------------------------------- Name : partition_drop_schedule Purpose : To store the retention period of partitions of : indivisual tables. This data is used by partition_drop : procedure to automatically drop partitions. ----------------------------------------------------------------------*/ create table partition_drop_schedule (
OWNER VARCHAR2(30), TABLE_NAME VARCHAR2(30), PART_SCHEME VARCHAR2(10), RETENTION_DURATION NUMBER(5), RETENTION_UNIT VARCHAR2(10) ) / comment on table partition_drop_schedule is 'To store the retention period of partitions of individual tables. This data is used by partition_drop procedure to automatically drop partitions.'
/
comment on column partition_drop_schedule.owner is 'the owner of the table' / comment on column partition_drop_schedule.table_name is 'the name of the table' / comment on column partition_drop_schedule.part_scheme is 'the partitioning time scheme (DAILY/WEEKLY/MONTHLY/QUARTERLY/HALFYEARLY/YEARLY) of the table' / comment on column partition_drop_schedule.RETENTION_DURATION is 'the duration of retention of the partition in number' / comment on column partition_drop_schedule.RETENTION_UNIT is 'the unit of duration of retention (DAY/WEEK/MONTH/QUARTER/HALFYEAR/YEAR of the table' /
Here are some sample inserts into the table:
insert into partition_drop_schedule values ( 'ARUP','TEST','MONTHLY',12,'MONTH' ) /
commit
/
Next comes the stored procedure:
CREATE OR REPLACE procedure SYS.partition_drop as cursor sched_cur is select * from partition_drop_schedule ; l_sched_rec sched_cur%rowtype; l_cutoff_date date; l_hv date; l_sql varchar2(2000); l_dis_cons_sql varchar2(2000); l_ena_cons_sql varchar2(2000); l_drop_sql varchar2(2000); errm varchar2(2000); l_out varchar2(2000); l_full varchar2(32000); l_recipients varchar2(2000) := 'dba.ops@myowncompany.com'; errc number(10); l_place varchar2(2); l_cons_disabled boolean := FALSE; type l_varchar2_30_ty is table of varchar2(30); l_child_owner l_varchar2_30_ty; l_child_table_name l_varchar2_30_ty; l_constraint_name l_varchar2_30_ty; CURSOR cons_cur ( p_owner in varchar2, p_table_name in varchar2 ) is select owner, table_name, constraint_name from dba_constraints where constraint_type = 'R' and status = 'ENABLED' and r_constraint_name = ( select constraint_name from dba_constraints where owner = p_owner and table_name = p_table_name and constraint_type = 'P' ); begin -- if the email server is not set already, set it here execute immediate 'alter session set smtp_out_server = ''smtp.myowncompany.com'''; l_out := rpad('Owner',10)|| rpad('Table',30)|| rpad('Retention',10)|| rpad('Cutoff Dt',10) ; dbms_output.put_line(l_out); l_full := l_out; l_out := rpad('-',9,'-')||' '|| rpad('-',29,'-')||' '|| rpad('-',9,'-')||' '|| rpad('-',9,'-') ; l_full := l_full||chr(12)||l_out; dbms_output.put_line(l_out); open sched_cur; loop fetch sched_cur into l_sched_rec; exit when sched_cur%notfound; l_out := rpad(l_sched_rec.owner,10); l_out := l_out||rpad(l_sched_rec.table_name,30); l_cutoff_date := case l_sched_rec.PART_SCHEME when 'MONTHLY' then case l_sched_rec.retention_unit when 'MONTH' then add_months(sysdate,-1*l_sched_rec.retention_duration) else null end when 'DAILY' then case l_sched_rec.retention_unit when 'DAY' then sysdate-l_sched_rec.retention_duration else null end when 'HALFYEARLY' then case l_sched_rec.retention_unit when 'HALFYEAR' then add_months(sysdate,-6*l_sched_rec.retention_duration) when 'MONTH' then add_months(sysdate,-1*l_sched_rec.retention_duration) else null end when 'YEARLY' then null when 'WEEKLY' then null when 'QUARTERLY' then case l_sched_rec.retention_unit when 'QUARTER' then add_months(sysdate,-3*l_sched_rec.retention_duration) when 'MONTH' then add_months(sysdate,-1*l_sched_rec.retention_duration) else null end end; l_out := l_out||rpad(l_sched_rec.retention_duration||' '||l_sched_rec.retention_unit,10); l_out := l_out||rpad(l_cutoff_date,10); dbms_output.put_line(l_out); l_full := l_full||chr(12)||l_out; for part_cur in ( select partition_name, high_value from dba_tab_partitions where table_owner = l_sched_rec.owner and table_name = l_sched_rec.table_name order by partition_position ) loop -- dbms_output.put_line('l_sched_rec.owner='||l_sched_rec.owner); -- dbms_output.put_line('l_sched_rec.table_name='||l_sched_rec.table_name); if part_cur.high_value != 'MAXVALUE' then execute immediate 'select '||part_cur.high_value|| ' from dual' into l_hv; if l_cutoff_date > l_hv then l_out := 'Partition '|| part_cur.partition_name|| ' with high value '|| l_hv|| ' to be dropped ...'; dbms_output.put_line(l_out); l_full := l_full||chr(12)||l_out; l_drop_sql := 'alter table '||l_sched_rec.owner||'.'|| l_sched_rec.table_name|| ' drop partition '||part_cur.partition_name|| ' update global indexes'; dbms_output.put_line('l_drop_sql='||l_drop_sql); begin -- -- Disable the FKs -- l_cons_disabled := FALSE; open cons_cur (l_sched_rec.owner, l_sched_rec.table_name); fetch cons_cur bulk collect into l_child_owner, l_child_table_name, l_constraint_name; close cons_cur; if nvl(l_child_owner.COUNT,0) > 0 then l_cons_disabled := TRUE; for ctr in l_child_owner.FIRST .. l_child_owner.LAST loop dbms_output.put_line('l_child_owner='||l_child_owner(ctr)); dbms_output.put_line('l_child_table_name='||l_child_table_name(ctr)); dbms_output.put_line('l_constraint_name='||l_constraint_name(ctr)); l_dis_cons_sql := 'alter table '||l_child_owner(ctr)||'.'|| l_child_table_name(ctr)||' disable constraint '|| l_constraint_name(ctr); dbms_output.put_line('l_dis_cons_sql='||l_dis_cons_sql); l_sql := l_dis_cons_sql; execute immediate l_sql; end loop; end if; l_sql := l_drop_sql; execute immediate l_sql; -- -- Enable the FKs -- if (l_cons_disabled) then for ctr in l_child_owner.FIRST .. l_child_owner.LAST loop l_dis_cons_sql := 'alter table '||l_child_owner(ctr)||'.'|| l_child_table_name(ctr)||' enable novalidate constraint '|| l_constraint_name(ctr); dbms_output.put_line('l_dis_cons_sql='||l_dis_cons_sql); l_sql := l_dis_cons_sql; execute immediate l_sql; end loop; end if; l_out := l_out||'DONE'; dbms_output.put_line(l_sql); dbms_output.put_line(l_out); l_full := l_full||'DONE'; l_full := l_full||chr(12)||l_sql; exception when others then errm := SQLERRM; errc := SQLCODE; l_out := l_out||'FAILED'; dbms_output.put_line(l_sql); dbms_output.put_line('Place:'||l_place); l_full := l_full||'FAILED'; l_full := l_full||chr(12)||chr(12)||l_sql; end; end if; end if; end loop; end loop; close sched_cur; dbms_output.put_line(l_full); utl_mail.send( 'oracle@prodb1',l_recipients, null, null, 'Succeeded: PROPRD Partition Drop', l_full ); exception when OTHERS then errm := SQLERRM; utl_mail.send( 'oracle@prodb1',l_recipients, null, null, 'Failed: PROPRD Partition Drop', l_full ); raise_application_error(-20001,errm); end; / show errorNow that the procedure is complete, you should do a quick test to make sure the procedure is working as expected. Update the record in the table PARTITION_DROP_SCHEDULE to reflect 1 month retention and execute this procedure. It should drop the partition and maintain the global indexes. You can confirm the actions by:
(1) checking the DBA_TAB_PARTITIONS to see that the partition is indeed gone
(2) checking the DBA_INDEXES to see that the global index is indeed in USABLE state
If all looks well, you should just put it in a DBMS_SCHEDULER job and set it to run every day at a time that will be considered off peak, e.g. 3 AM.
Important Considerations
- This is not intended for mass partition drops. For instance if you are planning to drop 100 partitions of a table, do not use it. This tool will update global indexes for each partition - a process highly inefficient for large numbers of partitions. You are much better off making the global indexes unusable, dropping all the partitions and then rebuilding global indexes. That will be much faster.
- At the time of the drop, the session will get an exclusive lock on the table for a fraction of a second. So you will see a slight performance degradation for that instant only.
- Never use this tool without adequately testing. Partition drop is a DDL operation - an irreversible process.
As always, I would appreciate comments and suggestions from you on this and any other blog entries. Thanks for reading and happy tooling.
4 comments:
Hi Arup,
You should consider foreign keys referencing unique (in addition to primary) key constraints:
...
and constraint_type in ('P','U')
...
Second, you may want to handle exception -54 (resource busy) somehow, maybe through session-level DDL_LOCK_TIMEOUT in 11g or manually (catch, sleep, retry) in 10g.
Cheers,
Flado
Some very interesting points but i think your research and bias leaves a lot to be desired. Then of course, that’s just my opinion. Have a great day definitely a thought-provoking post.
I glad to find such nice article. It is really a nice sharing .i usually visit this nice blog and get many information. Keep it up thanks.
Nice Article. I wonder why oracle does not provide any utility/package for this ?
Post a Comment