Oracle
 sql >> Database >  >> RDS >> Oracle

Affrontare il bug della colonna di rilascio in Oracle 18c e 19c

La strada del progresso può essere a volte accidentata. Le versioni Oracle 18 e 19 non fanno eccezione. Fino alla versione 18.x Oracle non ha avuto problemi a contrassegnare le colonne come inutilizzate e alla fine a lasciarle cadere. Date alcune circostanze interessanti, le ultime due versioni di Oracle possono generare errori ORA-00600 quando le colonne vengono impostate come inutilizzate e quindi eliminate. Le condizioni che causano questo errore potrebbero non essere comuni, ma esiste un gran numero di installazioni Oracle in tutto il mondo ed è molto probabile che qualcuno, da qualche parte, incontrerà questo bug.

Il racconto inizia con due tavoli e un trigger:

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;
/

I dati vengono inseriti nella tabella TRG_TST1 e, purché le condizioni siano soddisfatte, i dati vengono replicati nella tabella TRG_TST2. Due righe vengono inserite in TRG_TST1 in modo che solo una delle righe inserite venga copiata in TRG_TST2. Dopo ogni tabella di inserimento TRG_TST2 viene interrogata e vengono visualizzati i risultati:

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 > 

Ora inizia il "divertimento":due colonne in TST_TRG1 sono contrassegnate come "non utilizzate" e quindi eliminate e la tabella TST_TRG2 viene troncata. Gli inserti in TST_TRG1 vengono eseguiti di nuovo, ma questa volta vengono prodotti i temuti errori ORA-00600. Per vedere perché si verificano questi errori, lo stato del trigger viene riportato da 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 > 

Il problema è che, in Oracle 18c e 19c, l'azione "elimina colonne inutilizzate" NON invalida il trigger lasciandolo in uno stato "VALID" e impostando le transazioni successive per il fallimento. Poiché il trigger non è stato ricompilato alla chiamata successiva, l'ambiente di compilazione originale è ancora attivo, un ambiente che include le colonne ora eliminate. Oracle non riesce a trovare le colonne C1 e C2, ma il trigger si aspetta ancora che esistano, quindi l'errore ORA-00600. Il mio supporto Oracle segnala questo come un bug:

Bug 30404639 : TRIGGER DOES NOT WORK CORRECTLY AFTER ALTER TABLE DROP UNUSED COLUMN.

e segnala che la causa è, in effetti, la mancata invalidazione del trigger con la caduta di colonna differita.

Quindi come aggirare questo problema? Un modo è compilare in modo esplicito il trigger dopo che le colonne inutilizzate sono state eliminate:

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 > 

Con il trigger che ora utilizza l'ambiente corrente e la configurazione della tabella, gli inserimenti funzionano correttamente e il trigger si attiva come previsto:

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 > 

Esiste un altro modo per aggirare questo problema; Non contrassegnare le colonne come inutilizzate e semplicemente rilasciarle dalla tabella. Eliminare le tabelle originali, ricrearle ed eseguire questo esempio con un'eliminazione diretta di una colonna non mostra alcun segno di ORA-00600 e lo stato di attivazione dopo l'eliminazione della colonna dimostra che non verranno generati errori di questo tipo:

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 > 

Le versioni di Oracle precedenti alla 18c si comportano come previsto, con l'eliminazione della colonna posticipata che imposta correttamente lo stato del trigger su "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 >

Il modo in cui le colonne vengono eliminate nelle versioni precedenti alla 18c non fa differenza poiché tutti i trigger sulla tabella interessata verranno resi non validi. La successiva chiamata a qualsiasi trigger su quella tabella risulterà in una ricompilazione "automatica", impostando correttamente l'ambiente di esecuzione (il che significa che le colonne mancanti nella tabella interessata non rimarranno nel contesto di esecuzione).

Non è probabile che un database di produzione subisca riduzioni di colonna senza prima apportare tali modifiche in un database DEV o TST. Sfortunatamente, il test degli inserti dopo l'eliminazione delle colonne potrebbe non essere un test eseguito dopo che tali modifiche sono state apportate e prima che il codice venga promosso a PRD. Avere più di una persona che testa gli effetti collaterali della caduta delle colonne sembrerebbe un'idea eccellente, poiché, come attesta il vecchio adagio, "Due teste sono meglio di una". di un possibile fallimento può essere presentato ed eseguito. Il tempo extra impiegato per testare in modo più approfondito una modifica significa una possibilità meno probabile che errori imprevisti influiscano gravemente o interrompano la produzione.

# # #

Vedi gli articoli di David Fitzjarrell