Пътят на прогреса понякога може да бъде труден. Версии на 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. Да имаш повече от един човек, който тества последствията от изпускането на колони, изглежда отлична идея, тъй като, както свидетелства старата поговорка, „Две глави са по-добри от една.“ Колкото повече, толкова по-добре в ситуация на тестване, така че много възможности на възможен отказ може да бъде представен и изпълнен. Допълнителното време, необходимо за по-задълбочено тестване на промяна, означава по-малко вероятен шанс за непредвидени грешки, които сериозно засегнат или спират производството.
# # #
Вижте статии отДейвид Фицярел