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