Jalan kemajuan terkadang sulit. Oracle versi 18 dan 19 tidak terkecuali. Hingga versi 18.x Oracle tidak memiliki masalah dengan menandai kolom sebagai tidak digunakan dan akhirnya menjatuhkannya. Mengingat beberapa keadaan yang menarik, dua versi Oracle terbaru dapat menimbulkan kesalahan ORA-00600 ketika kolom ditetapkan sebagai tidak digunakan dan kemudian dihapus. Kondisi yang menyebabkan kesalahan ini mungkin tidak umum tetapi ada sejumlah besar instalasi Oracle di seluruh dunia dan kemungkinan besar seseorang, di suatu tempat, akan menemukan bug ini.
Kisah ini dimulai dengan dua meja dan sebuah pemicu:
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 dimasukkan ke dalam tabel TRG_TST1 dan, dengan syarat terpenuhi, data direplikasi ke tabel TRG_TST2. Dua baris dimasukkan ke TRG_TST1 sehingga hanya satu dari baris yang disisipkan yang akan disalin ke TRG_TST2. Setelah setiap tabel penyisipan TRG_TST2 ditanyai dan hasilnya ditampilkan:
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 >
Sekarang 'kesenangan' dimulai — dua kolom di TST_TRG1 ditandai 'tidak digunakan' dan kemudian dibuang, dan tabel TST_TRG2 terpotong. Penyisipan ke TST_TRG1 dieksekusi lagi, tetapi kali ini kesalahan ORA-00600 yang ditakuti dihasilkan. Untuk melihat mengapa kesalahan ini terjadi, status pemicu dilaporkan dari 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 >
Masalahnya adalah, di Oracle 18c dan 19c, tindakan 'jatuhkan kolom yang tidak digunakan' TIDAK membatalkan pemicu sehingga membiarkannya dalam status 'VALID' dan mengatur transaksi berikutnya untuk gagal. Karena pemicu tidak dikompilasi ulang pada panggilan berikutnya, lingkungan kompilasi asli masih berlaku, lingkungan yang menyertakan kolom yang sekarang dijatuhkan. Oracle tidak dapat menemukan kolom C1 dan C2, tetapi pemicunya masih mengharapkan kolom tersebut ada, sehingga kesalahan ORA-00600. Dukungan Oracle saya melaporkan ini sebagai bug:
Bug 30404639 : TRIGGER DOES NOT WORK CORRECTLY AFTER ALTER TABLE DROP UNUSED COLUMN.
dan melaporkan bahwa penyebab sebenarnya adalah kegagalan untuk membatalkan pemicu dengan penurunan kolom yang ditangguhkan.
Jadi bagaimana untuk menyiasati masalah ini? Salah satu caranya adalah dengan mengompilasi pemicu secara eksplisit setelah kolom yang tidak digunakan dihapus:
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 >
Dengan pemicu yang sekarang menggunakan lingkungan dan konfigurasi tabel saat ini, sisipan berfungsi dengan benar dan pemicu menyala seperti yang diharapkan:
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 >
Ada cara lain untuk mengatasi masalah ini; Jangan tandai kolom sebagai tidak terpakai dan jatuhkan saja dari tabel. Menjatuhkan tabel asli, membuatnya kembali, dan menjalankan contoh ini dengan penurunan kolom lurus tidak menunjukkan tanda ORA-00600, dan status pemicu setelah penurunan kolom membuktikan bahwa tidak ada kesalahan seperti itu yang akan terjadi:
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 >
Versi Oracle sebelum 18c berperilaku seperti yang diharapkan, dengan penurunan kolom yang ditangguhkan dengan benar mengatur status pemicu ke '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 >
Bagaimana kolom dijatuhkan dalam versi yang lebih lama dari 18c tidak ada bedanya karena pemicu apa pun pada tabel yang terpengaruh akan dianggap tidak valid. Panggilan berikutnya ke pemicu apa pun di tabel itu akan menghasilkan kompilasi ulang 'otomatis', yang menyetel lingkungan eksekusi dengan benar (artinya kolom yang hilang di tabel yang terpengaruh tidak akan tetap berada dalam konteks eksekusi).
Tidak mungkin database produksi akan mengalami penurunan kolom tanpa terlebih dahulu membuat perubahan seperti itu di database DEV atau TST. Sayangnya, pengujian sisipan setelah kolom dijatuhkan mungkin bukan pengujian yang dijalankan setelah perubahan tersebut dibuat dan sebelum kode dipromosikan ke PRD. Memiliki lebih dari satu orang untuk menguji efek samping dari menjatuhkan kolom tampaknya merupakan ide yang sangat baik, karena, seperti yang dibuktikan oleh pepatah lama, 'Dua kepala lebih baik dari satu.' Semakin banyak semakin meriah dalam situasi pengujian sehingga banyak jalan kemungkinan kegagalan dapat disajikan dan dieksekusi. Waktu ekstra yang dibutuhkan untuk menguji perubahan secara lebih menyeluruh berarti kecilnya kemungkinan kesalahan tak terduga yang secara serius memengaruhi, atau menghentikan, produksi.
# # #
Lihat artikel oleh David Fitzjarrell