Oracle
 sql >> Base de données >  >> RDS >> Oracle

Résolution du bogue de suppression de colonne dans Oracle 18c et 19c

La route du progrès peut parfois être rude. Les versions 18 et 19 d'Oracle ne font pas exception. Jusqu'à la version 18.x, Oracle n'avait aucun problème à marquer les colonnes comme inutilisées et à les supprimer éventuellement. Dans certaines circonstances intéressantes, les deux dernières versions d'Oracle peuvent générer des erreurs ORA-00600 lorsque des colonnes sont définies comme inutilisées, puis supprimées. Les conditions qui provoquent cette erreur ne sont peut-être pas courantes, mais il existe un grand nombre d'installations Oracle à travers le monde et il est très probable que quelqu'un, quelque part, rencontre ce bogue.

Le conte commence par deux tables et un déclencheur :

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;
/

Les données sont insérées dans la table TRG_TST1 et, si les conditions sont remplies, les données sont répliquées dans la table TRG_TST2. Deux lignes sont insérées dans TRG_TST1 afin qu'une seule des lignes insérées soit copiée dans TRG_TST2. Après chaque insertion, la table TRG_TST2 est interrogée et les résultats affichés :

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 > 

Maintenant, le "fun" commence - deux colonnes de TST_TRG1 sont marquées "inutilisées" puis supprimées, et la table TST_TRG2 est tronquée. Les insertions dans TST_TRG1 sont exécutées à nouveau, mais cette fois les redoutables erreurs ORA-00600 sont produites. Pour voir pourquoi ces erreurs se produisent, l'état du déclencheur est signalé par 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 > 

Le problème est que, dans Oracle 18c et 19c, l'action "supprimer les colonnes inutilisées" n'invalide PAS le déclencheur en le laissant dans un état "VALIDE" et en mettant les prochaines transactions en échec. Étant donné que le déclencheur n'a pas été recompilé lors de l'appel suivant, l'environnement de compilation d'origine est toujours en vigueur, un environnement qui inclut les colonnes désormais supprimées. Oracle ne peut pas trouver les colonnes C1 et C2, mais le déclencheur s'attend toujours à ce qu'elles existent, d'où l'erreur ORA-00600. Mon support Oracle signale cela comme un bogue :

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

et signale que la cause est, en fait, l'échec de l'invalidation du déclencheur avec la suppression de colonne différée.

Alors comment contourner ce problème ? Une façon consiste à compiler explicitement le déclencheur après la suppression des colonnes inutilisées :

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 > 

Avec le déclencheur utilisant maintenant l'environnement actuel et la configuration de la table, les insertions fonctionnent correctement et le déclencheur se déclenche comme prévu :

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 > 

Il existe un autre moyen de contourner ce problème; Ne marquez pas les colonnes comme inutilisées et supprimez-les simplement du tableau. Supprimer les tables d'origine, les recréer et exécuter cet exemple avec une suppression de colonne directe ne montre aucun signe d'ORA-00600, et l'état du déclencheur après la suppression de colonne prouve qu'aucune erreur de ce type ne sera générée :

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 > 

Les versions d'Oracle antérieures à 18c se comportent comme prévu, la suppression de colonne différée définissant correctement l'état du déclencheur sur "INVALIDE" :

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 >

La façon dont les colonnes sont supprimées dans les versions antérieures à 18c ne fait aucune différence car tous les déclencheurs de la table affectée seront rendus invalides. Le prochain appel à n'importe quel déclencheur sur cette table entraînera une recompilation "automatique", définissant correctement l'environnement d'exécution (ce qui signifie que les colonnes manquantes dans la table affectée ne resteront pas dans le contexte d'exécution).

Il est peu probable qu'une base de données de production subisse des suppressions de colonnes sans apporter d'abord de telles modifications dans une base de données DEV ou TST. Malheureusement, le test des insertions après la suppression des colonnes peut ne pas être un test exécuté après que ces modifications ont été apportées et avant que le code ne soit promu en PRD. Avoir plus d'une personne pour tester les séquelles de la chute des colonnes semble être une excellente idée, puisque, comme l'atteste le vieil adage, "Deux têtes valent mieux qu'une". d'échec possible peut être présenté et exécuté. Le temps supplémentaire nécessaire pour tester plus en profondeur un changement signifie moins de risques d'erreurs imprévues affectant gravement ou arrêtant la production.

# # #

Voir les articles de David Fitzjarrell