sql >> Databasteknik >  >> RDS >> Oracle

Åtgärda Drop Column Bug i Oracle 18c och 19c

Vägen till framsteg kan ibland vara svår. Oracle version 18 och 19 är inget undantag. Fram till version 18.x hade Oracle inga problem med att markera kolumner som oanvända och så småningom ta bort dem. Med tanke på några intressanta omständigheter kan de senaste två Oracle-versionerna orsaka ORA-00600-fel när kolumner ställs in som oanvända och sedan tas bort. Tillstånden som orsakar detta fel kanske inte är vanliga men det finns ett stort antal Oracle-installationer över hela världen och det är mycket troligt att någon någonstans kommer att stöta på detta fel.

Sagan börjar med två tabeller och en trigger:

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 infogas i tabellen TRG_TST1 och, förutsatt att villkoren är uppfyllda, replikeras data till tabellen TRG_TST2. Två rader infogas i TRG_TST1 så att endast en av de infogade raderna kommer att kopieras till TRG_TST2. Efter varje infogning avfrågas tabell TRG_TST2 och resultaten visas:

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 > 

Nu börjar det "roliga" - två kolumner i TST_TRG1 är markerade som "oanvända" och släpps sedan, och tabellen TST_TRG2 trunkeras. Insättningarna i TST_TRG1 exekveras igen, men den här gången produceras de fruktade ORA-00600-felen. För att se varför dessa fel uppstår rapporteras statusen för utlösaren från 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 > 

Problemet är att, i Oracle 18c och 19c, ogiltigförklarar åtgärden "släpp oanvända kolumner" INTE triggern och lämnar den i ett "GILTIGT" tillstånd och ställer in nästa transaktioner för misslyckande. Eftersom utlösaren inte kompilerades om vid nästa anrop är den ursprungliga kompileringsmiljön fortfarande aktiv, en miljö som inkluderar de nu släppta kolumnerna. Oracle kan inte hitta kolumnerna C1 och C2, men triggern förväntar sig fortfarande att de finns, alltså ORA-00600-felet. Min Oracle Support rapporterar detta som en bugg:

Bug 30404639 : TRIGGER DOES NOT WORK CORRECTLY AFTER ALTER TABLE DROP UNUSED COLUMN.

och rapporterar att orsaken i själva verket är misslyckandet med att ogiltigförklara triggern med det uppskjutna kolumnfallet.

Så hur kan man komma runt denna fråga? Ett sätt är att explicit kompilera triggern efter att de oanvända kolumnerna har släppts:

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 > 

När utlösaren nu använder den aktuella miljön och tabellkonfigurationen fungerar infogningarna korrekt och utlösaren aktiveras som förväntat:

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 > 

Det finns ett annat sätt att komma runt denna fråga; Markera inte kolumnerna som oanvända och släpp dem helt enkelt från tabellen. Att släppa de ursprungliga tabellerna, återskapa dem och exekvera det här exemplet med ett rakt kolumnsläpp visar inga tecken på en ORA-00600, och triggerstatusen efter kolumnsläppet bevisar att inga sådana fel kommer att kastas:

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-versioner före 18c beter sig som förväntat, med det uppskjutna kolumnfallet som korrekt sätter triggerstatusen till "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 >

Hur kolumnerna tas bort i versioner äldre än 18c gör ingen skillnad eftersom alla utlösare i den berörda tabellen kommer att göras ogiltiga. Nästa anrop till valfri utlösare i den tabellen kommer att resultera i en "automatisk" omkompilering, vilket ställer in exekveringsmiljön korrekt (vilket innebär att de saknade kolumnerna i den berörda tabellen inte kommer att finnas kvar i körningskontexten).

Det är inte troligt att en produktionsdatabas kommer att genomgå kolumnfall utan att först göra sådana ändringar i en DEV- eller TST-databas. Tyvärr är det inte säkert att testinlägg efter att kolumner har släppts är ett test som exekveras efter att sådana ändringar har gjorts och innan koden flyttas upp till PRD. Att ha mer än en person som testar efterverkningarna av att tappa kolumner verkar vara en utmärkt idé, eftersom, som det gamla ordspråket intygar, "Två huvuden är bättre än ett." Ju fler desto roligare i en testsituation så att många vägar eventuella fel kan presenteras och utföras. Den extra tid det tar att noggrant testa en förändring innebär en mindre sannolikhet för oförutsedda fel som allvarligt påverkar eller stoppar produktionen.

# # #

Se artiklar avDavid Fitzjarrell


  1. PostgreSQL:The Versatile INSERT

  2. LEN() vs DATALENGTH() i SQL Server

  3. Hur man returnerar element från en JSON-array i MariaDB

  4. Ställa in nätverkstidsgräns för JDBC-anslutning