Oracle
 sql >> база данни >  >> RDS >> Oracle

Справяне с грешката в Drop Column в Oracle 18c и 19c

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

# # #

Вижте статии отДейвид Фицярел


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Обяснение на оператора Oracle UNION

  2. 7 неща, които трябва да знаете за отделенията в Oracle Cloud Infrastructure

  3. Как да обновите материализиран изглед в oracle

  4. SQL „И“ или „ИЛИ“ е на първо място?

  5. Динамичен SQL LOOP