Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Resolvendo o bug de drop Column no Oracle 18c e 19c


O caminho do progresso pode ser difícil às vezes. As versões 18 e 19 do Oracle não são exceção. Até a versão 18.x, o Oracle não tinha problemas com a marcação de colunas como não utilizadas e, eventualmente, eliminá-las. Dadas algumas circunstâncias interessantes, as duas versões mais recentes do Oracle podem gerar erros ORA-00600 quando as colunas são definidas como não utilizadas e depois descartadas. As condições que causam esse erro podem não ser comuns, mas há um grande número de instalações Oracle em todo o mundo e é muito provável que alguém, em algum lugar, encontre esse bug.

A história começa com duas tabelas e um gatilho:
create table trg_tst1 (c0 varchar2(30), c1 varchar2(30), c2 varchar2(30), c3 varchar2(30), c4 varchar2(30));
create table trg_tst2 (c_log varchar2(30));

create or replace trigger trg_tst1_cpy_val
after insert or update on trg_tst1
for each row
begin
        IF :new.c3 is not null then
                insert into trg_tst2 values (:new.c3);
        end if;
end;
/

Os dados são inseridos na tabela TRG_TST1 e, desde que as condições sejam atendidas, os dados são replicados na tabela TRG_TST2. Duas linhas são inseridas em TRG_TST1 para que apenas uma das linhas inseridas seja copiada para TRG_TST2. Após cada inserção, a tabela TRG_TST2 é consultada e os resultados são exibidos:
SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c3) values ('Inserting c3 - should log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c4) values ('Inserting c4 - should not log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 

Agora a 'diversão' começa - duas colunas em TST_TRG1 são marcadas como 'não usadas' e depois descartadas, e a tabela TST_TRG2 é truncada. As inserções em TST_TRG1 são executadas novamente, mas desta vez são produzidos os temidos erros ORA-00600. Para ver por que esses erros ocorrem, o status do gatilho é relatado em USER_OBJECTS:
SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > --  ===================================
SMERBLE @ gwunkus > --  Drop some columns in two steps then
SMERBLE @ gwunkus > --  truncate trg_tst2 and repeat the test
SMERBLE @ gwunkus > --
SMERBLE @ gwunkus > --  ORA-00600 errors are raised
SMERBLE @ gwunkus > --
SMERBLE @ gwunkus > --  The trigger is not invalidated and
SMERBLE @ gwunkus > --  thus is not recompiled.
SMERBLE @ gwunkus > --  ===================================
SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > alter table trg_tst1 set unused (c1, c2);

Table altered.

SMERBLE @ gwunkus > alter table trg_tst1 drop unused columns;

Table altered.

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > select object_name, status from user_objects where object_name in (select trigger_name from user_triggers);


OBJECT_NAME                         STATUS
----------------------------------- -------
TRG_TST1_CPY_VAL                    VALID

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > truncate table trg_tst2;

Table truncated.

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c3) values ('Inserting c3 - should log');

insert into trg_tst1(c3) values ('Inserting c3 - should log')
            *
ERROR at line 1:
ORA-00600: internal error code, arguments: [insChkBuffering_1], [4], [4], [], [], [], [], [], [], [], [], []


SMERBLE @ gwunkus > select * from trg_tst2;

no rows selected

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c4) values ('Inserting c4 - should not log');

insert into trg_tst1(c4) values ('Inserting c4 - should not log')
            *
ERROR at line 1:
ORA-00600: internal error code, arguments: [insChkBuffering_1], [4], [4], [], [], [], [], [], [], [], [], []


SMERBLE @ gwunkus > select * from trg_tst2;

no rows selected

SMERBLE @ gwunkus > 

O problema é que, no Oracle 18c e 19c, a ação 'soltar colunas não utilizadas' NÃO invalida o gatilho deixando-o em um estado 'VALID' e configurando as próximas transações para falha. Como o gatilho não foi recompilado na próxima chamada, o ambiente de compilação original ainda está em vigor, um ambiente que inclui as colunas agora descartadas. O Oracle não consegue encontrar as colunas C1 e C2, mas o gatilho ainda espera que elas existam, daí o erro ORA-00600. Meu Suporte Oracle relata isso como um bug:
Bug 30404639 : TRIGGER DOES NOT WORK CORRECTLY AFTER ALTER TABLE DROP UNUSED COLUMN.

e relata que a causa é, de fato, a falha em invalidar o gatilho com a queda de coluna adiada.

Então, como contornar esse problema? Uma maneira é compilar explicitamente o gatilho depois que as colunas não utilizadas são descartadas:
SMERBLE @ gwunkus > --
SMERBLE @ gwunkus > -- Compile the trigger after column drops
SMERBLE @ gwunkus > --
SMERBLE @ gwunkus > alter trigger trg_tst1_cpy_val compile;

Trigger altered.

SMERBLE @ gwunkus > 

Com o gatilho agora usando o ambiente atual e a configuração da tabela, as inserções funcionam corretamente e o gatilho é acionado conforme o esperado:
SMERBLE @ gwunkus > --
SMERBLE @ gwunkus > -- Attempt inserts again
SMERBLE @ gwunkus > --
SMERBLE @ gwunkus > insert into trg_tst1(c3) values ('Inserting c3 - should log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c4) values ('Inserting c4 - should not log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 

Existe outra maneira de contornar esse problema; Não marque as colunas como não utilizadas e simplesmente solte-as da tabela. Soltar as tabelas originais, recriá-las e executar este exemplo com uma queda de coluna direta não mostra nenhum sinal de um ORA-00600, e o status do gatilho após a queda da coluna prova que nenhum desses erros será lançado:
SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > drop table trg_tst1 purge;

Table dropped.

SMERBLE @ gwunkus > drop table trg_tst2 purge;

Table dropped.

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > --  ===================================
SMERBLE @ gwunkus > --  Re-run the example without marking
SMERBLE @ gwunkus > --  columns as 'unused'
SMERBLE @ gwunkus > --  ===================================
SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > create table trg_tst1 (c0 varchar2(30), c1 varchar2(30), c2 varchar2(30), c3 varchar2(30), c4 varchar2(30));

Table created.

SMERBLE @ gwunkus > create table trg_tst2 (c_log varchar2(30));

Table created.

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > create or replace trigger trg_tst1_cpy_val
  2  after insert or update on trg_tst1
  3  for each row
  4  begin
  5  	     IF :new.c3 is not null then
  6  		     insert into trg_tst2 values (:new.c3);
  7  	     end if;
  8  end;
  9  /

Trigger created.

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c3) values ('Inserting c3 - should log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c4) values ('Inserting c4 - should not log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > --  ===================================
SMERBLE @ gwunkus > --  Drop some columns,
SMERBLE @ gwunkus > --  truncate trg_tst2 and repeat the test
SMERBLE @ gwunkus > --
SMERBLE @ gwunkus > --  No ORA-00600 errors are raised as
SMERBLE @ gwunkus > --  the trigger is invalidated by the
SMERBLE @ gwunkus > --  DDL.  Oracle then recompiles the
SMERBLE @ gwunkus > --  invalid trigger.
SMERBLE @ gwunkus > --  ===================================
SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > alter table trg_tst1 drop (c1,c2);

Table altered.

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > select object_name, status from user_objects where object_name in (select trigger_name from user_triggers);

OBJECT_NAME                         STATUS
----------------------------------- -------
TRG_TST1_CPY_VAL                    INVALID

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > truncate table trg_tst2;

Table truncated.

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c3) values ('Inserting c3 - should log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 
SMERBLE @ gwunkus > insert into trg_tst1(c4) values ('Inserting c4 - should not log');

1 row created.

SMERBLE @ gwunkus > select * from trg_tst2;

C_LOG
------------------------------
Inserting c3 - should log

SMERBLE @ gwunkus > 

Versões do Oracle anteriores a 18c se comportam conforme o esperado, com a queda da coluna adiada definindo corretamente o status do gatilho como 'INVALID':
SMARBLE @ gwankus > select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
CORE	12.1.0.2.0	Production
TNS for Linux: Version 12.1.0.2.0 - Production
NLSRTL Version 12.1.0.2.0 - Production

SMARBLE @ gwankus >
SMARBLE @ gwankus > alter table trg_tst1 set unused (c1, c2);

Table altered.

SMARBLE @ gwankus > alter table trg_tst1 drop unused columns;

Table altered.

SMARBLE @ gwankus >
SMARBLE @ gwankus > select object_name, status from user_objects where object_name in (select trigger_name from user_triggers);

OBJECT_NAME			    STATUS
----------------------------------- -------
TRG_TST1_CPY_VAL		    INVALID

SMARBLE @ gwankus >

Como as colunas são descartadas em versões anteriores a 18c não faz diferença, pois quaisquer gatilhos na tabela afetada serão inválidos. A próxima chamada para qualquer gatilho nessa tabela resultará em uma recompilação ‘automática’, definindo o ambiente de execução corretamente (o que significa que as colunas ausentes na tabela afetada não permanecerão no contexto de execução).

Não é provável que um banco de dados de produção sofra quedas de coluna sem primeiro fazer essas alterações em um banco de dados DEV ou TST. Infelizmente, testar inserções depois que as colunas são descartadas pode não ser um teste executado após essas alterações serem feitas e antes do código ser promovido para PRD. Ter mais de uma pessoa testando os efeitos posteriores da queda de colunas parece ser uma excelente ideia, pois, como atesta o velho ditado, "Duas cabeças pensam melhor que uma". de possível falha podem ser apresentados e executados. O tempo extra necessário para testar mais detalhadamente uma mudança significa uma chance menos provável de erros imprevistos afetarem seriamente ou interromperem a produção.

# # #

Veja artigos de David Fitzjarrell