普通表改成分区表的方法
有一系列普通表都有几十到几百GB这么大,数据从几亿到几十亿,现在想将这些表改造成分区表,用其中的时间或者其他字段来做分区,允许有一段停机时间来停这些表相关的应用,该如何做呢?
思路:
新建一张分区表,按日期建分区,确保分区表各字段和属性都和普通表一样。然后停应用,将普通表记录插入到分区表中。然后将普通表重命名,分区表命名成原表的名字,完成任务。
将原表重命名为_yyyymmdd格式的表名:
create or replace procedure p_rename_001(p_tab in varchar2) as /* 功能:将原表重命名为_yyyymmdd格式的表名 完善点:要考虑RENMAE的目标表已存在的情况,先做判断 */ v_cnt_re_tab number(9) := 0; v_sql_p_rename varchar2(4000); yyyymmdd varchar2(8); begin select to_char(sysdate, 'yyyymmdd') into yyyymmdd from dual; select count(*) into v_cnt_re_tab from user_objects where object_name = upper(p_tab || '_' || yyyymmdd); if v_cnt_re_tab = 0 then v_sql_p_rename := 'rename ' || p_tab || ' to ' || p_tab || '_' || yyyymmdd; -- DBMS_OUTPUT.PUT_LINE(v_sql_p_rename);--调试使用 p_insert_log(p_tab, 'P_RENAME', v_sql_p_rename, '完成原表的重命名,改为_YYYYMMDD形式', 1); execute immediate (v_sql_p_rename); --这里无须做判断,rename动作真实完成!如果后续只是为生成脚本而不是真实执行分区操作,最后再把这个表RENAME回去! else raise_application_error(-20066, '备份表' || p_tab || '_' || yyyymmdd || '已存在,请先删除或重命名该备份表后再继续执行!'); -- DBMS_OUTPUT.PUT_LINE('备份表'||P_TAB||'_'||YYYYMMDD||'已存在'); end if; dbms_output.put_line('操作步骤1(备份原表)-------将' || p_tab || ' 表RENMAE成 ' || p_tab || '_' || yyyymmdd || ',并删除其约束索引等'); end p_rename_001;
用CREATE TABLE AS SELECT 的方式从RENAME的_yyyymmdd表中新建出一个只有MAXVALUE的初步分区表:
create or replace procedure p_ctas_002 ( p_tab in varchar2, p_struct_only in number, p_deal_flag in number, p_part_colum in varchar2, p_parallel in number default 4, p_tablespace in varchar2 ) as /* 功能:用CREATE TABLE AS SELECT 的方式从RENAME的_yyyymmdd表中新建出一个只有MAXVALUE的初步分区表 完善点:要考虑并行,nologging 的提速方式,也要考虑最终将NOLOGGING和PARALLEL恢复成正常状态 */ v_sql_p_ctas varchar2(4000); begin v_sql_p_ctas := 'create table ' || p_tab || ' partition by range ( ' || p_part_colum || ' ) (' || ' partition P_MAX values less than (maxvalue))' || ' nologging parallel 4 tablespace ' || p_tablespace || ' as select /*+parallel(t,' || p_parallel || ')*/ *' || ' from ' || p_tab || '_' || yyyymmdd; if p_struct_only = 0 then v_sql_p_ctas := v_sql_p_ctas || ' where 1=2'; else v_sql_p_ctas := v_sql_p_ctas || ' where 1=1'; end if; --DBMS_OUTPUT.PUT_LINE(v_sql_p_ctas);--调试使用 p_insert_log(p_tab, 'p_ctas', v_sql_p_ctas, '完成CTAS建初步分区表', 2, 1); p_if_judge(v_sql_p_ctas, p_deal_flag); v_sql_p_ctas := 'alter table ' || p_tab || ' logging'; p_insert_log(p_tab, 'p_ctas', v_sql_p_ctas, '将新分区表修改回LOGGING属性', 2, 2); p_if_judge(v_sql_p_ctas, p_deal_flag); v_sql_p_ctas := 'alter table ' || p_tab || ' noparallel'; p_insert_log(p_tab, 'p_ctas', v_sql_p_ctas, '将新分区表修改回NOPARALLEL属性', 2, 3); p_if_judge(v_sql_p_ctas, p_deal_flag); dbms_output.put_line('操作步骤2(建分区表)-------通过CTAS的方式从 ' || p_tab || '_' || yyyymmdd || ' 中新建' || p_tab || '表,完成初步分区改造工作'); end p_ctas_002;
对分区表进行分区SPLIT工作
create or replace procedure p_split_part_003 ( p_tab in varchar2, p_deal_flag in number, p_part_nums in number default 24, p_tab_tablespace in varchar2 ) as /* 功能:用CREATE TABLE AS SELECT 的方式新建出一个只有MAXVALUE的初步分区表进行SPLIT, 按月份进行切分,默认p_part_nums产生24个分区,构造2年的分区表,第一个分区为当前月的 上一个月 */ v_first_day date; v_next_day date; v_prev_day date; v_sql_p_split_part varchar2(4000); begin select to_date(to_char(sysdate, 'yyyymm') || '01', 'yyyymmdd') into v_first_day from dual; for i in 1 .. p_part_nums loop select add_months(v_first_day, i) into v_next_day from dual; select add_months(v_next_day, -1) into v_prev_day from dual; v_sql_p_split_part := 'alter table ' || p_tab || ' split partition p_MAX at ' || '(to_date(''' || to_char(v_next_day, 'yyyymmdd') || ''',''yyyymmdd''))' || 'into (partition PART_' || to_char(v_prev_day, 'yyyymm') || ' tablespace ' || p_tab_tablespace || ', partition p_MAX)'; -- DBMS_OUTPUT.PUT_LINE(v_sql_p_split_part);--调试使用 p_insert_log(p_tab, 'p_split_part', v_sql_p_split_part, '分区表完成分区SPLIT工作', 3, i); p_if_judge(v_sql_p_split_part, p_deal_flag); end loop; dbms_output.put_line('操作步骤3(分区操作)-------对新建的' || p_tab || '分区表完成分区SPLIT工作'); end p_split_part_003;
从_YYYYMMDD备份表中得到表的注释,为新分区表的表名增加注释
create or replace procedure p_tab_comments_004 ( p_tab in varchar2, p_deal_flag in number ) as /* 功能:从_YYYYMMDD备份表中得到表的注释,为新分区表的表名增加注释 */ v_sql_p_tab_comments varchar2(4000); v_cnt number; begin select count(*) into v_cnt from user_tab_comments where table_name = upper(p_tab) || '_' || yyyymmdd and comments is not null; if v_cnt > 0 then for i in (select * from user_tab_comments where table_name = upper(p_tab) || '_' || yyyymmdd and comments is not null) loop v_sql_p_tab_comments := 'comment on table ' || p_tab || ' is ' || '''' || i.comments || ''''; -- DBMS_OUTPUT.PUT_LINE(v_sql_p_deal_tab_comments);--调试使用 p_insert_log(p_tab, 'p_deal_comments', v_sql_p_tab_comments, '将新分区表的表的注释加上', 4, 1); p_if_judge(v_sql_p_tab_comments, p_deal_flag); end loop; dbms_output.put_line('操作步骤4(表的注释)-------对' || p_tab || '表增加表名的注释内容'); else dbms_output.put_line('操作步骤4(表的注释)-------' || upper(p_tab) || '_' || yyyymmdd || '并没有表注释!'); end if; end p_tab_comments_004;
从_YYYYMMDD备份表中得到表和字段的注释,为新分区表的表名和字段增加注释
create or replace procedure p_col_comments_005 ( p_tab in varchar2, p_deal_flag in number ) as /* 功能:从_YYYYMMDD备份表中得到表和字段的注释,为新分区表的表名和字段增加注释 */ v_sql_p_col_comments varchar2(4000); v_cnt number; begin select count(*) into v_cnt from user_col_comments where table_name = upper(p_tab) || '_' || yyyymmdd and comments is not null; if v_cnt > 0 then for i in (select * from user_col_comments where table_name = upper(p_tab) || '_' || yyyymmdd and comments is not null) loop v_sql_p_col_comments := 'comment on column ' || p_tab || '.' || i.column_name || ' is ' || '''' || i.comments || ''''; p_insert_log(p_tab, 'p_deal_col_comments', v_sql_p_col_comments, '将新分区表的列的注释加上', 5, 1); p_if_judge(v_sql_p_col_comments, p_deal_flag); end loop; dbms_output.put_line('操作步骤5(列的注释)-------对' || p_tab || '表增加列名及字段的注释内容'); else dbms_output.put_line('操作步骤5(列的注释)-------' || upper(p_tab) || '_' || yyyymmdd || '并没有列注释!'); end if; end p_col_comments_005;
从_YYYYMMDD备份表中得到原表的DEFAULT值,为新分区表的表名和字段增加DEFAULT值
create or replace procedure p_defau_and_null_006 ( p_tab in varchar2, p_deal_flag in number ) as /* 功能:从_YYYYMMDD备份表中得到原表的DEFAULT值,为新分区表的表名和字段增加DEFAULT值 */ v_sql_defau_and_null varchar2(4000); v_cnt number; begin select count(*) into v_cnt from user_tab_columns where table_name = upper(p_tab) || '_' || yyyymmdd and data_default is not null; if v_cnt > 0 then for i in (select * from user_tab_columns where table_name = upper(p_tab) || '_' || yyyymmdd and data_default is not null) loop v_sql_defau_and_null := 'alter table ' || p_tab || ' modify ' || i.column_name || ' default ' || i.data_default; p_insert_log(p_tab, 'p_deal_default', v_sql_defau_and_null, '将新分区表的默认值加上', 6); p_if_judge(v_sql_defau_and_null, p_deal_flag); end loop; dbms_output.put_line('操作步骤6(空和默认)-------对' || p_tab || '表完成默认DEFAULT值的增加'); else dbms_output.put_line('操作步骤6(空和默认)-------' || upper(p_tab) || '_' || yyyymmdd || '并没有DEFAULT或NULL值!'); end if; end p_defau_and_null_006;
从_YYYYMMDD备份表中得到原表的CHECK值,为新分区表增加CHECK值
create or replace procedure p_check_007 ( p_tab in varchar2, p_deal_flag in number ) as /* 功能:从_YYYYMMDD备份表中得到原表的CHECK值,为新分区表增加CHECK值 另注: user_constraints已经进行了非空的判断,可以略去如下类似的从user_tab_columns获取非空判断的代码编写来判断是否 for i in (select * from user_tab_columns where table_name=UPPER(P_TAB)||'_' ||YYYYMMDD and nullable='N') loop v_sql:='alter table '||p_tab||' modify '||i.COLUMN_NAME ||' not null'; */ v_sql_p_check varchar2(4000); v_cnt number; begin select count(*) into v_cnt from user_constraints where table_name = upper(p_tab) || '_' || yyyymmdd and constraint_type = 'C'; if v_cnt > 0 then for i in (select * from user_constraints where table_name = upper(p_tab) || '_' || yyyymmdd and constraint_type = 'C') loop v_sql_p_check := 'alter table ' || p_tab || '_' || yyyymmdd || ' drop constraint ' || i.constraint_name; p_insert_log(p_tab, 'p_deal_check', v_sql_p_check, '将备份出来的原表的CHECK删除', 7, 1); p_if_judge(v_sql_p_check, p_deal_flag); v_sql_p_check := 'alter table ' || p_tab || ' ADD CONSTRAINT ' || i.constraint_name || ' CHECK (' || i.search_condition || ')'; p_insert_log(p_tab, 'p_deal_check', v_sql_p_check, '将新分区表的CHECK加上', 7, 2); p_if_judge(v_sql_p_check, p_deal_flag); end loop; dbms_output.put_line('操作步骤7(check约束)-------对' || p_tab || '完成CHECK的约束'); else dbms_output.put_line('操作步骤7(check约束)-----' || upper(p_tab) || '_' || yyyymmdd || '并没有CHECK!'); end if; end p_check_007;
从_YYYYMMDD备份表中得到原表的索引信息,为新分区表增加普通索引(唯一和非唯一索引,函数索引暂不考虑),并删除旧表索引
create or replace procedure p_index_008 ( p_tab in varchar2, p_deal_flag in number, p_idx_tablespace in varchar2 ) as /* 功能:从_YYYYMMDD备份表中得到原表的索引信息,为新分区表增加普通索引(唯一和非唯一索引,函数索引暂不考虑),并删除旧表索引 难点:需要考虑联合索引的情况 */ v_sql_p_normal_idx varchar2(4000); v_cnt number; begin select count(*) into v_cnt from user_indexes where table_name = upper(p_tab) || '_' || yyyymmdd and index_type = 'NORMAL' and index_name not in (select constraint_name from user_constraints); if v_cnt > 0 then for i in (with t as (select c.*, i.uniqueness from user_ind_columns c, (select distinct index_name, uniqueness from user_indexes where table_name = upper(p_tab) || '_' || yyyymmdd and index_type = 'NORMAL' and index_name not in (select constraint_name from user_constraints)) i where c.index_name = i.index_name) select index_name, table_name, uniqueness, max(substr(sys_connect_by_path(column_ name, ','), 2)) str ---考虑组合索引的情况 from (select column_name, index_name, table_name, row_number() over(partition by index_name, table_name order by column_name) rn, uniqueness from t) t start with rn = 1 connect by rn = prior rn + 1 and index_name = prior index_name group by index_name, t.table_name, uniqueness) loop v_sql_p_normal_idx := 'drop index ' || i.index_name; p_insert_log(p_tab, 'p_deal_normal_idx', v_sql_p_normal_idx, '删除原表索引', 8, 1); p_if_judge(v_sql_p_normal_idx, p_deal_flag); dbms_output.put_line('操作步骤8(处理索引)-------将' || i.table_name || '的' || i.str || '列的索引' || i.index_name || '删除完毕'); if i.uniqueness = 'UNIQUE' then v_sql_p_normal_idx := 'CREATE UNIQUE INDEX ' || i.index_name || ' ON ' || p_tab || '(' || i.str || ')' || ' tablespace ' || p_idx_tablespace; elsif i.uniqueness = 'NONUNIQUE' then v_sql_p_normal_idx := 'CREATE INDEX ' || i.index_name || ' ON ' || p_tab || ' (' || i.str || ')' || ' LOCAL tablespace ' || p_idx_tablespace; end if; p_insert_log(p_tab, 'p_deal_normal_idx', v_sql_p_normal_idx, '将新分区表的索引加上', 8, 2); p_if_judge(v_sql_p_normal_idx, p_deal_flag); dbms_output.put_line('操作步骤8(处理索引)-------对' || p_tab || '新分区表' || i.str || '列增加索引' || i.index_name); end loop; else dbms_output.put_line('操作步骤8(处理索引)-------' || upper(p_tab) || '_' || yyyymmdd || '并没有索引(索引模块并不含主键判断)!'); end if; end p_index_008;
从_YYYYMMDD备份表中得到原表的主键信息,为新分区表增加主键值,并删除旧表主键
create or replace procedure p_pk_009 ( p_tab in varchar2, p_deal_flag in number, p_idx_tablespace in varchar2 ) as /* 功能:从_YYYYMMDD备份表中得到原表的主键信息,为新分区表增加主键值,并删除旧表主键 难点:需要考虑联合主键的情况 */ v_sql_p_pk varchar2(4000); v_cnt number; begin select count(*) into v_cnt from user_ind_columns where index_name in (select index_name from sys.user_constraints t where table_name = upper(p_tab) || '_' || yyyymmdd and constraint_type = 'P'); if v_cnt > 0 then for i in (with t as (select index_name, table_name, column_name from user_ind_columns where index_name in (select index_name from sys.user_constraints t where table_name = upper(p_tab) || '_' || yyyymmdd and constraint_type = 'P')) select index_name, table_name, max(substr(sys_connect_by_path(column_name, ','), 2)) str from (select column_name, index_name, table_name, row_number() over(partition by index_name, table_name order by column_name) rn from t) t start with rn = 1 connect by rn = prior rn + 1 and index_name = prior index_name group by index_name, t.table_name) loop v_sql_p_pk := 'alter table ' || i.table_name || ' drop constraint ' || i.index_name || ' cascade'; p_insert_log(p_tab, 'p_deal_pk', v_sql_p_pk, '将备份出来的原表的主键删除', 9, 1); p_if_judge(v_sql_p_pk, p_deal_flag); dbms_output.put_line('操作步骤9(处理主键)-------将备份出来的原表' || i.table_name || '的' || i.str || '列的主键' || i.index_name || '删除完 毕!'); ---放在FOR循环中效率没问题,因为主键只有一个,只会循环一次 v_sql_p_pk := 'ALTER TABLE ' || p_tab || ' ADD CONSTRAINT ' || i.index_name || ' PRIMARY KEY (' || i.str || ')' || ' using index tablespace ' || p_idx_tablespace; p_insert_log(p_tab, 'p_deal_pk', v_sql_p_pk, '将新分区表的主键加上', 9, 2); p_if_judge(v_sql_p_pk, p_deal_flag); dbms_output.put_line('操作步骤9(处理主键)-------对' || p_tab || '表的' || i.str || '列增加主键' || i.index_name); ---放在FOR循环中效率没问题,因为主键只有一个,只会循环一次 end loop; else dbms_output.put_line('操作步骤9(处理主键)-------' || upper(p_tab) || '_' || yyyymmdd || '并没有主键!'); end if; end p_pk_009;
从_YYYYMMDD备份表中得到原表的外键或主键约束,为新分区表增加外键或主键,并删除旧表的外键或主键
create or replace procedure p_constraint_010 ( p_tab in varchar2, p_deal_flag in number ) as /* 功能:从_YYYYMMDD备份表中得到原表的约束,为新分区表增加约束值,并删除旧表约束 难点:需要考虑联合外键REFERENCE的情况 */ v_sql_p_constraint varchar2(4000); v_cnt number; begin select count(*) into v_cnt from user_constraints where table_name = upper(p_tab) || '_' || yyyymmdd and constraint_type = 'R'; if v_cnt > 0 then for i in (with t1 as (select /*+no_merge */ position, t.owner, t.constraint_name as constraint_name1, t.table_name as table_name1, t.column_name as column_name1 from user_cons_columns t where constraint_name in (select constraint_name from user_constraints where table_name = upper(p_tab) || '_' || yyyymmdd and constraint_type = 'R')), t2 as (select /*+no_merge */ t.position, c.constraint_name constraint_name1, t.constraint_name as constraint_name2, t.table_name as table_name2, t.column_name as column_name2, max(t.position) over(partition by c.constraint_name) max_position from user_cons_columns t, user_constraints c where c.table_name = upper(p_tab) || '_' || yyyymmdd and t.constraint_name = c.r_constraint_name and c.constraint_type = 'R'), t3 as (select t1.*, t2.constraint_name2, t2.table_name2, t2.column_name2, t2.max_position from t1, t2 where t1.constraint_name1 = t2.constraint_name1 and t1.position = t2.position) select t3.*, substr(sys_connect_by_path(column_name1, ','), 2) as fk, substr(sys_ connect_by_path(column_name2, ','), 2) as pk from t3 where position = max_position start with position = 1 connect by constraint_name1 = prior constraint_name1 and position = prior position + 1) loop v_sql_p_constraint := 'alter table ' || p_tab || '_' || yyyymmdd || ' drop constraint ' || i.constraint_name1; p_insert_log(p_tab, 'p_deal_constraint', v_sql_p_constraint, '删除原表FK外键', 10, 1); p_if_judge(v_sql_p_constraint, p_deal_flag); dbms_output.put_line('操作步骤10(处理外键)------将备份出来的' || i.table_name1 || '表的' || i.column_name1 || '列的外键' || i.constraint_name1 || '删除完毕!'); v_sql_p_constraint := 'alter table ' || p_tab || ' add constraint ' || i.constraint_ name1 || ' foreign key ( ' || i.fk || ') references ' || i.table_name2 || ' (' || i.pk || ' )'; p_insert_log(p_tab, 'p_deal_constraint', v_sql_p_constraint, '将新分区表的外键加上', 10, 2); p_if_judge(v_sql_p_constraint, p_deal_flag); dbms_output.put_line('操作步骤10(处理外键)------对' || p_tab || '表的' || i.column_ name1 || '列增加外键' || i.constraint_name1); end loop; else dbms_output.put_line('操作步骤10(处理外键)------' || upper(p_tab) || '_' || yyyymmdd || '并没有外键!'); end if; end p_constraint_010;