有一系列普通表都有幾十到幾百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;
