Particionamento ONLINE de Tabelas com DBMS_REDEFINITION

Neste artigo irei demonstrar como podemos redefinir a estrutura de uma tabela já existente e converte-lá em uma tabela particionada utilizando o modelo range(intervalo) através da package DBMS_REDEFINITION existente desde a versão 9i e que permite uma redefinição ONLINE de tabelas.


Para este exemplo, vamos utilizar uma tabela existente que tenho aqui e que não é particionada, por nome de PAGTO_ALUNOS_SEM_PARTICAO:

SQL> col OWNER format a20
col TABLE_NAME format a30
select OWNER, TABLE_NAME from dba_tables where table_name ='PAGTO_ALUNOS_SEM_PARTICAO';

OWNER                TABLE_NAME
-------------------- ------------------------------
TESTE                PAGTO_ALUNOS_SEM_PARTICAO

SQL>

Vamos incluir um campo chave nesta tabela, pois, sem ele não conseguimos fazer a redefinição online:

SQL> alter table TESTE.PAGTO_ALUNOS_SEM_PARTICAO add constraint pk_PAGTO_ALUNOS_SEM_PARTICAO primary key (id_pagto);

Table altered.

SQL>

Confira que nossa tabela não é particionada:

SQL> select table_name, partition_name, num_rows
 from dba_tab_partitions
where table_name='PAGTO_ALUNOS_SEM_PARTICAO'; 

no rows selected

SQL>

Certo, nosso primeiro passo é verificar se a tabela pode ser redefinida de forma online.
Caso não seja uma candidata, a procedure irá retornar um erro indicando o motivo.
Em nosso caso abaixo, foi tudo tranquilo:

SQL> BEGIN
DBMS_REDEFINITION.CAN_REDEF_TABLE(uname => 'TESTE', tname => 'PAGTO_ALUNOS_SEM_PARTICAO');
END;
/

PL/SQL procedure successfully completed.

SQL>

Neste exemplo não estou especificando a opção OPTIONS_FLAG, desta forma a procedure CAN_REDEF_TABLE utiliza a opção default DBMS_REDEFINITION.CONS_USE_PK que realiza a redefinição usando a chave-primária da tabela (condição existente na nossa tabela).
Caso a tabela não possua PK, podemos especificar OPTIONS_FLAG=>DBMS_REDEFINITION.CONS_USE_ROWID.
Mas apenas por curiosidade, se a nossa tabela não tivesse uma chave primária e você utiliza-se a opção default, nós receberiamos o erro abaixo:

SQL> BEGIN
DBMS_REDEFINITION.CAN_REDEF_TABLE(uname => 'TESTE', tname => 'PAGTO_ALUNOS_SEM_PARTICAO');
END;
/
BEGIN
*
ERROR at line 1:
ORA-12089: cannot online redefine table "TESTE"."PAGTO_ALUNOS_SEM_PARTICAO"
with no primary key
ORA-06512: at "SYS.DBMS_REDEFINITION", line 173
ORA-06512: at "SYS.DBMS_REDEFINITION", line 3664
ORA-06512: at line 2

Agora iremos criar nossa tabela temporária, conhecida por INTERIM, utilizando a mesma estrutura da tabela anterior (PAGTO_ALUNOS_SEM_PARTICAO) porém agora particionada e sem constraints/índices.

SQL> create table teste.pagto_alunos_interim
  2      ( id_pagto number not null,
  3        id_aluno number not null,
  4        valor_mensalidade number(10,2),
  5        data_boleto date,
  6        data_vencimento date
  7      ) partition by range (data_boleto)
  8       ( partition p_pagto_2015 values less than (to_date('31/12/2015','dd/mm/rrrr')) tablespace pagto_2015,
  9         partition p_pagto_2016 values less than (to_date('31/12/2016','dd/mm/rrrr')) tablespace pagto_2016,
 10         partition p_pagto_2017 values less than (to_date('31/12/2017','dd/mm/rrrr')) tablespace pagto_2017
 11  );

Table created.

SQL>

Conceda quota para o usuário poder criar objetos no tablespaces abaixo caso não tenha:

SQL> alter user teste quota unlimited on pagto_2015;

User altered.

SQL>
SQL> alter user teste quota unlimited on pagto_2016;

User altered.

SQL> alter user teste quota unlimited on pagto_2017;

User altered.

SQL>

O próximo passo é iniciar o processo de redefinição online da tabela, vejamos:

SQL> BEGIN
 DBMS_REDEFINITION.start_redef_table(
        uname      => 'TESTE',
        orig_table => 'PAGTO_ALUNOS_SEM_PARTICAO',
    int_table  => 'PAGTO_ALUNOS_INTERIM');
END;
/ 

PL/SQL procedure successfully completed.

SQL>

Você pode realizar uma contagem nas duas tabelas para comparar a quantidade de registros se quiser.
Ainda apenas por curiosidade, caso você tenha o erro abaixo quando iniciar o processo de redefinição, voce pode abortar o processo que foi iniciado com o: DBMS_REDEFINITION.abort_redef_table, como demonstrado abaixo.
Após ter abortado é só iniciar o processo de redefinição novamente (DBMS_REDEFINITION.start_redef_table).

ERROR at line 1:
ORA-23539: table "TESTE"."PAGTO_ALUNOS_SEM_PARTICAO" currently being redefined
ORA-06512: at "SYS.DBMS_REDEFINITION", line 75
ORA-06512: at "SYS.DBMS_REDEFINITION", line 3459
ORA-06512: at line 2

SQL>
SQL> BEGIN
DBMS_REDEFINITION.abort_redef_table(
uname      => 'TESTE',
orig_table => 'PAGTO_ALUNOS_SEM_PARTICAO',
int_table  => 'PAGTO_ALUNOS_INTERIM');
END;
/  

PL/SQL procedure successfully completed.

Dando sequência iremos realizar a cópia dos objetos dependentes para a tabela INTERIM, como grants, triggers, constraints, índices e privilégios se tiverem:

SQL> var num_errors number
SQL> BEGIN
  2
  3       DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS(uname => 'TESTE',
  4                                                                                     orig_table=>'PAGTO_ALUNOS_SEM_PARTICAO',
  5                                                                                     int_table=>'PAGTO_ALUNOS_INTERIM',
  6                                                                                     copy_indexes=>dbms_redefinition.cons_orig_params ,
  7                                                                                     copy_triggers=>TRUE,
  8                                                                                     copy_constraints=>TRUE,
  9                                                                                     copy_privileges=>TRUE,
 10                                                                                     ignore_errors=>TRUE,
 11                                                                                     num_errors=>:num_errors,
 12                                                                                     copy_statistics=>false);
 13
 14  END;
 15  /

PL/SQL procedure successfully completed.

Visualize os erros com a consulta abaixo e se achar necessário continue o procedimento:

SQL> col object_owner for a10
col base_table_name for a20
col ddl_txt for a30

select object_type, object_owner, base_table_name, ddl_txt
  from dba_redefinition_errors;

OBJECT_TYPE  OBJECT_OWN BASE_TABLE_NAME      DDL_TXT
------------ ---------- -------------------- ------------------------------
CONSTRAINT   TESTE      PAGTO_ALUNOS_SEM_PAR ALTER TABLE "TESTE"."PAGTO_ALU
                        TICAO                NOS_INTERIM" MODIFY ("ID_PAGTO
                                             " CONSTRAINT "TMP$$_

CONSTRAINT   TESTE      PAGTO_ALUNOS_SEM_PAR ALTER TABLE "TESTE"."PAGTO_ALU
                        TICAO                NOS_INTERIM" MODIFY ("ID_ALUNO
                                             " CONSTRAINT "TMP$$_
SQL>

Certo, vamos disparar um sincronismo para manter a tabela INTERIM sincronizada com a tabela original(PAGTO_ALUNOS_SEM_PARTICAO).
No nosso cenário não temos nenhum benefício, pois ninguém esta alterando ou inserindo registros na tabela PAGTO_ALUNOS_SEM_PARTICAO, contudo em um ambiente OLTP cuja tabela esta sofrendo constantes operações DML o sincronismo é útil para minimizar a quantidade de sincronização necessária a ser feita pelo procedimento FINISH_REDEF_TABLE, acelerando as operações subsequentes:

SQL> BEGIN
    DBMS_REDEFINITION.sync_interim_table(
      uname      => 'TESTE',
      orig_table => 'PAGTO_ALUNOS_SEM_PARTICAO',
      int_table  => 'PAGTO_ALUNOS_INTERIM');
END;
/

PL/SQL procedure successfully completed.

Por fim, disparamos uma coleta de estatística sobre a PAGTO_ALUNOS_INTERIM e finalizamos o processo de redefinição.

SQL> BEGIN
  2      DBMS_STATS.GATHER_TABLE_STATS(ownname          => 'TESTE',
  3                                    TABNAME          => 'PAGTO_ALUNOS_INTERIM',
  4                                    estimate_percent => 100,
  5                                    method_opt       => 'FOR ALL COLUMNS SIZE 1',
  6                                    degree           => 16,
  7                                    granularity      => 'ALL',
  8                                    cascade          => TRUE);
  9  END;
 10  /

PL/SQL procedure successfully completed.

SQL>
  
SQL> BEGIN
    DBMS_REDEFINITION.finish_redef_table(
      uname      => 'TESTE',
      orig_table => 'PAGTO_ALUNOS_SEM_PARTICAO',
      int_table  => 'PAGTO_ALUNOS_INTERIM');
END;
/

PL/SQL procedure successfully completed.

Agora já podemos remover a tabela INTERIM(PAGTO_ALUNOS_INTERIM):

SQL> drop table teste.pagto_alunos_interim;

Table dropped.


SQL> col table_name format a30
col partition_name format a30
col num_rows format 999999999.99

select table_name, partition_name, num_rows
  from dba_tab_partitions
 where table_name='PAGTO_ALUNOS_SEM_PARTICAO';

TABLE_NAME                     PARTITION_NAME                      NUM_ROWS
------------------------------ ------------------------------ -------------
PAGTO_ALUNOS_SEM_PARTICAO      P_PAGTO_2015                       498679.00
PAGTO_ALUNOS_SEM_PARTICAO      P_PAGTO_2016                       501420.00
PAGTO_ALUNOS_SEM_PARTICAO      P_PAGTO_2017                       499901.00

SQL>

Agora nossa tabela já populada sem partição está particionada rs.

Abraço,

Ronaldo.

Nenhum comentário:

Postar um comentário