Пътят на прогреса понякога може да бъде труден. Версии на Oracle 18 и 19 не са изключение. До версия 18.x Oracle нямаше проблеми с маркирането на колони като неизползвани и евентуално изпускането им. Като се имат предвид някои интересни обстоятелства, последните две версии на Oracle могат да хвърлят грешки ORA-00600, когато колоните са зададени като неизползвани и след това отпаднат. Условията, които причиняват тази грешка, може да не са често срещани, но има голям брой инсталации на Oracle по целия свят и е много вероятно някой някъде да срещне този бъг.
Историята започва с две маси и тригер:
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; /
Данните се вмъкват в таблица TRG_TST1 и при условие, че са изпълнени условията, данните се репликират в таблица TRG_TST2. Два реда се вмъкват в TRG_TST1, така че само един от вмъкнатите редове ще бъде копиран в TRG_TST2. След всяко вмъкване на таблица TRG_TST2 се запитва и резултатите се показват:
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 >
Сега започва „забавлението“ – две колони в TST_TRG1 са маркирани като „неизползвани“ и след това отпадат, а таблицата TST_TRG2 е съкратена. Вмъкванията в TST_TRG1 се изпълняват отново, но този път се получават ужасните грешки ORA-00600. За да видите защо възникват тези грешки, състоянието на тригера се отчита от 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 >
Проблемът е, че в Oracle 18c и 19c действието „изпускане на неизползваните колони“ НЕ обезсилва тригера, оставяйки го в състояние „ВАЛИДНО“ и настройва следващите транзакции за неуспех. Тъй като тригерът не е прекомпилиран при следващото извикване, оригиналната среда за компилиране все още е в сила, среда, която включва изпуснатите сега колони. Oracle не може да намери колони C1 и C2, но тригерът все още очаква те да съществуват, поради което грешката ORA-00600. Моята поддръжка на Oracle отчита това като грешка:
Bug 30404639 : TRIGGER DOES NOT WORK CORRECTLY AFTER ALTER TABLE DROP UNUSED COLUMN.
и съобщава, че причината всъщност е неуспехът да се анулира задействането с отложеното отпадане на колона.
И така, как да заобиколите този проблем? Един от начините е изрично да компилирате тригера, след като неизползваните колони бъдат премахнати:
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 >
С тригера, който сега използва текущата среда и конфигурация на таблицата, вмъкванията функционират правилно и тригерът се задейства както се очаква:
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 >
Съществува и друг начин за заобикаляне на този проблем; Не маркирайте колоните като неизползвани и просто ги пуснете от таблицата. Отпадането на оригиналните таблици, пресъздаването им и изпълнението на този пример с право падане на колона не показва признаци на ORA-00600, а състоянието на задействане след отпадането на колоната доказва, че няма да бъдат изхвърлени такива грешки:
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 >
Версиите на Oracle преди 18c се държат според очакванията, като отложената колона правилно задава състоянието на задействане на „НЕВАЛИДНО“:
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 >
Начинът, по който колоните се изпускат във версии, по-стари от 18c, няма значение, тъй като всички тригери в засегнатата таблица ще бъдат направени невалидни. Следващото извикване на който и да е тригер в тази таблица ще доведе до „автоматично“ прекомпилиране, като се настрои правилно средата на изпълнение (което означава, че липсващите колони в засегнатата таблица няма да останат в контекста на изпълнение).
Малко вероятно е производствена база данни да претърпи отпадане на колони, без първо да направи такива промени в DEV или TST база данни. За съжаление тестването на вмъквания след отпадане на колони може да не е тест, който се изпълнява след направени такива промени и преди кодът да бъде повишен в PRD. Да имаш повече от един човек, който тества последствията от изпускането на колони, изглежда отлична идея, тъй като, както свидетелства старата поговорка, „Две глави са по-добри от една.“ Колкото повече, толкова по-добре в ситуация на тестване, така че много възможности на възможен отказ може да бъде представен и изпълнен. Допълнителното време, необходимо за по-задълбочено тестване на промяна, означава по-малко вероятен шанс за непредвидени грешки, които сериозно засегнат или спират производството.
# # #
Вижте статии отДейвид Фицярел