Droga postępu może być czasami trudna. Wersje Oracle 18 i 19 nie są wyjątkiem. Aż do wersji 18.x firma Oracle nie miała problemów z oznaczaniem kolumn jako nieużywanych i ich usuwaniem. Biorąc pod uwagę kilka interesujących okoliczności, ostatnie dwie wersje Oracle mogą generować błędy ORA-00600, gdy kolumny są ustawione jako nieużywane, a następnie usuwane. Warunki, które powodują ten błąd, mogą nie być powszechne, ale na całym świecie istnieje duża liczba instalacji Oracle i jest bardzo prawdopodobne, że ktoś gdzieś napotka ten błąd.
Opowieść zaczyna się od dwóch tabel i wyzwalacza:
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; /
Dane są wstawiane do tabeli TRG_TST1 i, jeśli warunki są spełnione, dane są replikowane do tabeli TRG_TST2. Dwa wiersze są wstawiane do TRG_TST1, tak że tylko jeden z wstawionych wierszy zostanie skopiowany do TRG_TST2. Po każdym wstawieniu tabeli TRG_TST2 jest odpytywany i wyświetlane są wyniki:
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 >
Teraz zaczyna się „zabawa” — dwie kolumny w TST_TRG1 są oznaczone jako „nieużywane”, a następnie usuwane, a tabela TST_TRG2 jest obcinana. Wstawki do TST_TRG1 są ponownie wykonywane, ale tym razem pojawiają się przerażające błędy ORA-00600. Aby zobaczyć, dlaczego występują te błędy, stan wyzwalacza jest zgłaszany przez 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 >
Problem polega na tym, że w Oracle 18c i 19c akcja „upuść nieużywane kolumny” NIE unieważnia wyzwalacza, pozostawiając go w stanie „WAŻNY” i ustawiając kolejne transakcje na niepowodzenie. Ponieważ wyzwalacz nie został ponownie skompilowany przy następnym wywołaniu, oryginalne środowisko kompilacji nadal działa, środowisko zawierające teraz porzucone kolumny. Oracle nie może znaleźć kolumn C1 i C2, ale wyzwalacz nadal oczekuje ich istnienia, stąd błąd ORA-00600. Moja pomoc techniczna Oracle zgłasza to jako błąd:
Bug 30404639 : TRIGGER DOES NOT WORK CORRECTLY AFTER ALTER TABLE DROP UNUSED COLUMN.
i zgłasza, że przyczyną jest w rzeczywistości brak unieważnienia wyzwalacza z odroczonym spadkiem kolumny.
Jak więc obejść ten problem? Jednym ze sposobów jest jawna kompilacja wyzwalacza po usunięciu nieużywanych kolumn:
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 >
Gdy wyzwalacz korzysta teraz z bieżącego środowiska i konfiguracji tabeli, wstawki działają poprawnie, a wyzwalacz uruchamia się zgodnie z oczekiwaniami:
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 >
Istnieje inny sposób na obejście tego problemu; Nie oznaczaj kolumn jako nieużywanych i po prostu usuń je z tabeli. Usunięcie oryginalnych tabel, odtworzenie ich i wykonanie tego przykładu z prostym upuszczeniem kolumny nie wykazuje oznak ORA-00600, a stan wyzwalacza po upuszczeniu kolumny dowodzi, że takie błędy nie zostaną zgłoszone:
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 >
Wersje Oracle przed 18c zachowują się zgodnie z oczekiwaniami, a odroczone upuszczenie kolumny prawidłowo ustawia stan wyzwalacza na „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 >
Sposób, w jaki kolumny są usuwane w wersjach starszych niż 18c, nie ma znaczenia, ponieważ wszelkie wyzwalacze w tabeli, której dotyczy problem, zostaną unieważnione. Następne wywołanie dowolnego wyzwalacza w tej tabeli spowoduje „automatyczną” ponowną kompilację, prawidłowo ustawiając środowisko wykonywania (co oznacza, że brakujące kolumny w tabeli, której dotyczy problem, nie pozostaną w kontekście wykonywania).
Jest mało prawdopodobne, że produkcyjna baza danych zostanie poddana upuszczaniu kolumn bez uprzedniego wprowadzenia takich zmian w bazie danych DEV lub TST. Niestety testowanie wstawek po usunięciu kolumn może nie być testem wykonywanym po wprowadzeniu takich zmian i przed promocją kodu do PRD. Posiadanie więcej niż jednej osoby testującej następstwa upuszczania kolumn wydaje się być doskonałym pomysłem, ponieważ, jak potwierdza stare powiedzenie, „co dwie głowy to nie jedna.” Im więcej, tym weselej w sytuacji testowej, tak wiele możliwości możliwej awarii można przedstawić i wykonać. Dodatkowy czas poświęcony na dokładniejsze przetestowanie zmiany oznacza mniejsze prawdopodobieństwo wystąpienia nieprzewidzianych błędów, które poważnie wpłyną lub zatrzymają produkcję.
# # #
Zobacz artykuły Davida Fitzjarrella