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