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