Cesta pokroku může být někdy drsná. Oracle verze 18 a 19 nejsou výjimkou. Až do verze 18.x neměl Oracle žádné problémy s označováním sloupců jako nepoužité a případně s jejich vypouštěním. Vzhledem k některým zajímavým okolnostem mohou poslední dvě verze Oracle způsobit chyby ORA-00600, když jsou sloupce nastaveny jako nepoužité a poté odstraněny. Podmínky, které tuto chybu způsobují, nemusí být běžné, ale na celém světě existuje velké množství instalací Oracle a je velmi pravděpodobné, že se s touto chybou někdo někde setká.
Příběh začíná dvěma tabulkami a spouštěčem:
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; /
Data jsou vložena do tabulky TRG_TST1 a za předpokladu splnění podmínek jsou data replikována do tabulky TRG_TST2. Do TRG_TST1 se vloží dva řádky, takže do TRG_TST2 bude zkopírován pouze jeden z vložených řádků. Po každém vložení tabulky je dotazován TRG_TST2 a výsledky jsou zobrazeny:
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 >
Nyní začíná ‚zábava‘ – dva sloupce v TST_TRG1 jsou označeny jako ‚nepoužité‘ a poté vypuštěny a tabulka TST_TRG2 je zkrácena. Vložení do TST_TRG1 se provedou znovu, ale tentokrát se vytvoří obávané chyby ORA-00600. Chcete-li zjistit, proč k těmto chybám dochází, je stav spouštěče hlášen z 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 >
Problém je v tom, že v Oracle 18c a 19c akce „zahození nepoužívaných sloupců“ NEZneplatní spouštěč a ponechá jej ve stavu „PLATNÝ“ a nastaví další transakce na selhání. Vzhledem k tomu, že spouštěč nebyl při příštím volání znovu zkompilován, stále platí původní prostředí kompilace, prostředí, které obsahuje nyní vynechané sloupce. Oracle nemůže najít sloupce C1 a C2, ale spouštěč stále očekává jejich existenci, takže chyba ORA-00600. Moje podpora Oracle to hlásí jako chybu:
Bug 30404639 : TRIGGER DOES NOT WORK CORRECTLY AFTER ALTER TABLE DROP UNUSED COLUMN.
a hlásí, že příčinou je ve skutečnosti selhání zneplatnění spouštěče s odloženým poklesem sloupce.
Jak tedy tento problém obejít? Jedním ze způsobů je explicitně zkompilovat spouštěč po vynechání nepoužívaných sloupců:
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 >
Když spouštěč nyní používá aktuální prostředí a konfiguraci tabulky, vkládání funguje správně a spouštěč se spustí podle očekávání:
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 >
Existuje jiný způsob, jak tento problém obejít; Neoznačujte sloupce jako nepoužité a jednoduše je vypusťte z tabulky. Zrušení původních tabulek, jejich opětovné vytvoření a provedení tohoto příkladu s přímým vypuštěním sloupce nevykazuje žádné známky ORA-00600 a stav spouštění po vypuštění sloupce dokazuje, že žádné takové chyby nebudou vyvolány:
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 >
Verze Oracle starší než 18c se chovají podle očekávání, přičemž odložený pokles sloupce správně nastavil stav spouštění na „NEPLATNÉ“:
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 >
Na tom, jak jsou sloupce odstraněny ve verzích starších než 18c, nezáleží, protože všechny spouštěče v dotčené tabulce budou neplatné. Další volání libovolného spouštěče v této tabulce bude mít za následek ‚automatickou‘ rekompilaci, která správně nastaví spouštěcí prostředí (což znamená, že chybějící sloupce v dotčené tabulce nezůstanou v kontextu provádění).
Není pravděpodobné, že v produkční databázi dojde k poklesu sloupců, aniž by předtím provedli takové změny v databázi DEV nebo TST. Bohužel testování vložení po vynechání sloupců nemusí být testem, který se provede po provedení takových změn a před povýšením kódu na PRD. Mít více než jednu osobu, která by testovala následky padajících sloupů, se zdá být skvělým nápadem, protože, jak dosvědčuje staré přísloví, „Dvě hlavy jsou lepší než jedna.“ Čím více, tím lépe v testovací situaci, takže mnoho cest možného selhání lze předložit a provést. Dodatečný čas potřebný k důkladnějšímu otestování změny znamená menší pravděpodobnost, že nepředvídané chyby vážně ovlivní nebo zastaví výrobu.
# # #
Viz články odDavida Fitzjarrella