Trigger adalah objek skema bernama yang diaktifkan server secara otomatis
ketika peristiwa INSERT, UPDATE, atau DELETE terjadi pada tabel yang
ditunjuknya. Topik ini membahas sintaks CREATE TRIGGER, perbedaan waktu
BEFORE dan AFTER, akses kolom OLD dan NEW, serta penerapan trigger
untuk pencatatan audit pada tabel terpisah. Contohnya memakai basis data
perpustakaan yang dilengkapi tabel anggota_log untuk merekam setiap
perubahan data anggota. Setelah mengikuti topik ini, perubahan data penting
dapat tercatat otomatis tanpa mengubah satu pun pernyataan aplikasi.
Trigger pertama#
CREATE TRIGGER mendefinisikan trigger dengan urutan lima bagian, yaitu nama,
waktu (BEFORE atau AFTER), peristiwa (INSERT, UPDATE, atau DELETE),
tabel pemicunya, dan tubuh pernyataan yang dijalankan FOR EACH ROW.
Sifat FOR EACH ROW menjadikan trigger berlevel baris, yaitu tubuhnya
dijalankan sekali untuk setiap baris yang terdampak pernyataan pemicu. Satu
UPDATE yang mengubah sepuluh baris menjalankan trigger yang sama sepuluh
kali, sehingga volume operasi massal langsung memengaruhi beban trigger.
Jalankan persiapan berikut untuk membangun ulang basis data perpustakaan
beserta tabel audit anggota_log.
-- tidak-dijalankan
-- Skrip persiapan topik trigger: basis data perpustakaan dengan tabel audit
DROP DATABASE IF EXISTS perpustakaan;
CREATE DATABASE perpustakaan;
USE perpustakaan;
CREATE TABLE anggota (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100) NOT NULL
);
CREATE TABLE buku (
id INT AUTO_INCREMENT PRIMARY KEY,
judul VARCHAR(200) NOT NULL,
penulis VARCHAR(100) NOT NULL,
stok INT NOT NULL DEFAULT 0
);
CREATE TABLE peminjaman (
id INT AUTO_INCREMENT PRIMARY KEY,
anggota_id INT NOT NULL,
buku_id INT NOT NULL,
tanggal_pinjam DATE NOT NULL,
jatuh_tempo DATE NOT NULL,
tanggal_kembali DATE NULL,
FOREIGN KEY (anggota_id) REFERENCES anggota(id),
FOREIGN KEY (buku_id) REFERENCES buku(id)
);
-- tabel audit untuk mencatat setiap perubahan data anggota
CREATE TABLE anggota_log (
id INT AUTO_INCREMENT PRIMARY KEY,
anggota_id INT NOT NULL,
aksi VARCHAR(10) NOT NULL,
nama VARCHAR(100) NOT NULL,
keterangan VARCHAR(200) NOT NULL
);
INSERT INTO anggota (nama) VALUES
('Ali'), ('Budi'), ('Citra'), ('Dewi'), ('Eka');
INSERT INTO buku (judul, penulis, stok) VALUES
('Dasar Astronomi untuk Pemula', 'Hartono', 3),
('Sejarah Maritim Nusantara', 'Wijaya', 2),
('Panduan Menanam Sayuran', 'Santoso', 4),
('Pengantar Fisika Klasik', 'Kusuma', 1),
('Cerita Rakyat Nusantara', 'Lestari', 3),
('Ilmu Gizi Sehari-hari', 'Pratiwi', 2);
INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam, jatuh_tempo, tanggal_kembali) VALUES
(1, 1, '2026-05-02', '2026-05-16', '2026-05-15'),
(2, 4, '2026-05-10', '2026-05-24', '2026-05-22'),
(3, 2, '2026-06-05', '2026-06-19', NULL),
(5, 5, '2026-06-14', '2026-06-28', NULL);# membuat ulang basis data perpustakaan dengan tabel audit anggota_log
mysql -u root < persiapan-trigger.sqlTrigger pertama mencatat setiap pendaftaran anggota baru ke tabel audit
anggota_log. Tubuhnya berupa satu pernyataan INSERT, dan kolomnya
mengambil nilai dari NEW, yaitu baris yang baru saja disimpan.
# trigger audit AFTER INSERT pada tabel anggota
mysql -u root perpustakaan <<'SQL'
DROP TRIGGER IF EXISTS anggota_sisip;
CREATE TRIGGER anggota_sisip AFTER INSERT ON anggota
FOR EACH ROW
INSERT INTO anggota_log (anggota_id, aksi, nama, keterangan)
VALUES (NEW.id, 'INSERT', NEW.nama, 'pendaftaran anggota baru');
SQL# memverifikasi trigger dengan sisipan nyata
mysql -t -u root perpustakaan <<'SQL'
INSERT INTO anggota (nama) VALUES ('Fajar'), ('Gita');
SELECT id, anggota_id, aksi, nama, keterangan FROM anggota_log ORDER BY id;
SQL+----+------------+--------+-------+--------------------------+
| id | anggota_id | aksi | nama | keterangan |
+----+------------+--------+-------+--------------------------+
| 1 | 6 | INSERT | Fajar | pendaftaran anggota baru |
| 2 | 7 | INSERT | Gita | pendaftaran anggota baru |
+----+------------+--------+-------+--------------------------+Setiap sisipan anggota memicu satu baris log, dan kolom anggota_id
mengambil nilai NEW.id dari baris yang baru tersimpan. Lima anggota yang
disisipkan oleh skrip persiapan tidak muncul di log karena trigger baru dibuat
setelahnya. Trigger hanya bereaksi terhadap perubahan yang terjadi selama ia
ada, sehingga urutan pembuatan trigger dan pengisian data menentukan cakupan
audit.
Kolom OLD dan NEW#
Di dalam tubuh trigger, MySQL menyediakan dua baris semu. Kolom NEW memuat
nilai yang akan disimpan pada peristiwa INSERT dan UPDATE, sedangkan kolom
OLD memuat nilai sebelum perubahan pada peristiwa UPDATE dan DELETE.
Ketersediaannya mengikuti peristiwa, sebagaimana tabel berikut.
| Peristiwa | OLD | NEW |
|---|---|---|
INSERT | tidak ada | tersedia, dapat ditulis pada BEFORE |
UPDATE | tersedia | tersedia, dapat ditulis pada BEFORE |
DELETE | tersedia | tidak ada |
NEW hanya dapat ditulis pada trigger BEFORE, sehingga perbaikan nilai
dilakukan di sana sebelum baris tersimpan. Trigger berikut mengunci stok agar
tidak pernah negatif, dengan mengganti nilai yang akan disimpan menjadi nol
apabila sisipannya negatif.
# trigger BEFORE INSERT memperbaiki nilai NEW sebelum baris tersimpan
mysql -u root perpustakaan <<'SQL'
DROP TRIGGER IF EXISTS buku_sebelum_sisip;
CREATE TRIGGER buku_sebelum_sisip BEFORE INSERT ON buku
FOR EACH ROW
SET NEW.stok = GREATEST(NEW.stok, 0);
SQL# stok negatif pada sisipan berubah menjadi nol
mysql -t -u root perpustakaan <<'SQL'
INSERT INTO buku (judul, penulis, stok) VALUES ('Atlas Hujan Tropis', 'Mahendra', -2);
SELECT id, judul, stok FROM buku WHERE id = 7;
SQL+----+--------------------+------+
| id | judul | stok |
+----+--------------------+------+
| 7 | Atlas Hujan Tropis | 0 |
+----+--------------------+------+Sisipan meminta stok negatif dua, tetapi baris yang tersimpan memuat nol
karena GREATEST(NEW.stok, 0) menulis ulang kolom NEW sebelum penyimpanan.
Kolom stok memang tidak memiliki constraint CHECK, sehingga trigger
BEFORE menjadi lapisan penjaganya. Pada trigger AFTER, NEW hanya dapat
dibaca karena barisnya sudah tersimpan, dan OLD selalu hanya dapat dibaca.
Untuk tabel dengan primary key yang tidak diubah oleh pembaruannya, OLD.id
dan NEW.id bernilai sama pada peristiwa UPDATE, sehingga baris log dapat
merujuk identitas yang tetap. Kolom-kolom ini juga hanya hidup di dalam tubuh
trigger, dan nilai NEW yang terlihat pada trigger AFTER sudah final,
yaitu nilai yang sungguh tersimpan termasuk nilai bawaan yang diisi server.
Audit tiga peristiwa#
Audit lengkap menuntut tiga trigger, satu untuk setiap peristiwa. Trigger
pembaruan membandingkan OLD dan NEW pada kolom yang sama, sedangkan trigger
penghapusan hanya memiliki OLD. Tubuh trigger boleh berupa satu pernyataan
seperti pada contoh pertama, atau blok BEGIN...END berisi banyak pernyataan,
dan begitu tubuhnya memuat ;, DELIMITER kembali diperlukan dengan cara yang
sama seperti saat membuat prosedur.
Dua trigger berikut melengkapi audit tabel anggota, keduanya dengan AFTER
dan tubuh blok BEGIN...END.
# trigger audit AFTER UPDATE dan AFTER DELETE pada tabel anggota
mysql -u root perpustakaan <<'SQL'
DELIMITER //
DROP TRIGGER IF EXISTS anggota_ubah //
CREATE TRIGGER anggota_ubah AFTER UPDATE ON anggota
FOR EACH ROW
BEGIN
INSERT INTO anggota_log (anggota_id, aksi, nama, keterangan)
VALUES (OLD.id, 'UPDATE', NEW.nama,
CONCAT('nama berubah dari "', OLD.nama, '" menjadi "', NEW.nama, '"'));
END //
DROP TRIGGER IF EXISTS anggota_hapus //
CREATE TRIGGER anggota_hapus AFTER DELETE ON anggota
FOR EACH ROW
BEGIN
INSERT INTO anggota_log (anggota_id, aksi, nama, keterangan)
VALUES (OLD.id, 'DELETE', OLD.nama, CONCAT('anggota ', OLD.nama, ' diarsipkan'));
END //
DELIMITER ;
SQLVerifikasi dilakukan dengan perubahan nyata, yaitu satu pembaruan nama dan satu
pengarsipan anggota. Sebelum anggota dihapus, baris peminjamannya dihapus
terlebih dahulu, karena foreign key anggota_id tidak mengizinkan penghapusan
anggota yang masih memilikinya.
# pembaruan dan penghapusan nyata untuk mengisi log audit
mysql -t -u root perpustakaan <<'SQL'
UPDATE anggota SET nama = 'Ali Yudhistira' WHERE id = 1;
DELETE FROM peminjaman WHERE anggota_id = 5;
DELETE FROM anggota WHERE id = 5;
SELECT id, anggota_id, aksi, nama, keterangan FROM anggota_log ORDER BY id;
SQL+----+------------+--------+----------------+--------------------------------------------------+
| id | anggota_id | aksi | nama | keterangan |
+----+------------+--------+----------------+--------------------------------------------------+
| 1 | 6 | INSERT | Fajar | pendaftaran anggota baru |
| 2 | 7 | INSERT | Gita | pendaftaran anggota baru |
| 3 | 1 | UPDATE | Ali Yudhistira | nama berubah dari "Ali" menjadi "Ali Yudhistira" |
| 4 | 5 | DELETE | Eka | anggota Eka diarsipkan |
+----+------------+--------+----------------+--------------------------------------------------+Empat baris log kini mencatat tiga jenis peristiwa. Baris UPDATE memuat nama
baru pada kolom nama dan menyebut nama lama pada keterangannya, karena OLD
dan NEW sama-sama tersedia pada trigger pembaruan. Baris DELETE hanya dapat
merujuk OLD, sehingga identitas terakhir anggota Eka terekam sebelum hilang.
AFTER dipilih untuk audit karena baris log hanya ditulis setelah perubahan
benar-benar tersimpan. Pekerjaan trigger dan pernyataan pemicunya berada dalam
satu transaksi, sehingga apabila pernyataan pemicunya gagal dan di-ROLLBACK,
baris lognya ikut dibatalkan. Dengan demikian log tidak pernah mencatat
perubahan yang tidak pernah terjadi.
Mengelola trigger#
Daftar trigger pada sebuah skema dapat dibaca dari kamus data
information_schema.triggers, dengan memilih kolom yang menunjukkan peristiwa,
tabel, dan waktunya. Klien mysql juga menyediakan SHOW TRIGGERS, tetapi
keluarannya memuat kolom waktu pembuatan sehingga tidak cocok untuk
perbandingan yang menuntut keluaran tetap. Nama trigger bersifat unik di
tingkat skema, bukan di tingkat tabel, dan penghapusannya memakai
DROP TRIGGER.
# daftar trigger pada skema perpustakaan dari kamus data
mysql -t -u root perpustakaan -e "SELECT trigger_name, event_manipulation, event_object_table, action_timing FROM information_schema.triggers WHERE trigger_schema = 'perpustakaan' ORDER BY trigger_name;"+--------------------+--------------------+--------------------+---------------+
| TRIGGER_NAME | EVENT_MANIPULATION | EVENT_OBJECT_TABLE | ACTION_TIMING |
+--------------------+--------------------+--------------------+---------------+
| anggota_hapus | DELETE | anggota | AFTER |
| anggota_sisip | INSERT | anggota | AFTER |
| anggota_ubah | UPDATE | anggota | AFTER |
| buku_sebelum_sisip | INSERT | buku | BEFORE |
+--------------------+--------------------+--------------------+---------------+Empat trigger terdaftar. Tiga di antaranya terpasang pada tabel anggota
untuk setiap peristiwa, sedangkan satu lagi terpasang pada tabel buku. Kolom
EVENT_MANIPULATION dan ACTION_TIMING bersama-sama menyatakan posisi trigger
dalam pola waktu peristiwa, misalnya AFTER untuk INSERT. Pola
DROP TRIGGER IF EXISTS sebelum CREATE TRIGGER membuat skrip penyiapan tetap
dapat dijalankan ulang.
Kamus data yang sama juga memuat kolom ACTION_STATEMENT berisi tubuh lengkap
setiap trigger, sehingga definisi yang ditulis lama tetap dapat ditinjau tanpa
membuka skrip aslinya. Menghapus trigger tidak menyentuh satu baris data pun
pada tabel pemicunya. Yang hilang hanyalah reaksi otomatisnya. Tabel audit yang
sudah terisi tetap utuh setelah trigger dihapus, dan baris log lama tetap
terbaca seperti biasa.
Kesalahan umum#
Kesalahan pertama adalah membuat trigger dengan nama yang sudah terpakai di skema yang sama. Karena namespace-nya setingkat skema, nama yang sama tetap ditolak meskipun tabel pemicunya berbeda.
# nama trigger yang sama dalam satu skema ditolak server
mysql -u root perpustakaan -e "CREATE TRIGGER anggota_sisip AFTER INSERT ON anggota FOR EACH ROW INSERT INTO anggota_log (anggota_id, aksi, nama, keterangan) VALUES (NEW.id, 'INSERT', NEW.nama, 'duplikat');"ERROR 1359 (HY000) at line 1: Trigger already existsKesalahan kedua adalah trigger yang mengubah tabel pemicunya sendiri, misalnya
mencoba memperbarui peminjaman dari dalam trigger AFTER INSERT ON peminjaman. Pernyataan pemicu masih memakai tabel tersebut, sehingga perubahan
di tengah pernyataan yang sama tidak mungkin dilakukan dan server menolaknya
saat sisipan berjalan.
# trigger yang mengubah tabel pemicunya sendiri
mysql -u root perpustakaan <<'SQL'
DELIMITER //
DROP TRIGGER IF EXISTS peminjaman_perbarui_sendiri //
CREATE TRIGGER peminjaman_perbarui_sendiri AFTER INSERT ON peminjaman
FOR EACH ROW
BEGIN
UPDATE peminjaman SET jatuh_tempo = jatuh_tempo WHERE id = NEW.id;
END //
DELIMITER ;
SQL
mysql -u root perpustakaan -e "INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam, jatuh_tempo) VALUES (1, 2, '2026-07-02', '2026-07-16');"ERROR 1442 (HY000) at line 1: Can't update table 'peminjaman' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.Perbaikannya bukan memindahkan perubahan ke trigger lain, melainkan menulisnya langsung di pernyataan aplikasi atau menyimpan hasilnya ke tabel yang berbeda. Trigger peraga tersebut dihapus agar tidak mengganggu sisipan berikutnya pada basis data latihan.
# menghapus trigger peraga
mysql -u root perpustakaan -e "DROP TRIGGER IF EXISTS peminjaman_perbarui_sendiri;"Kesalahan ketiga adalah menulis tubuh trigger tanpa FOR EACH ROW, yang
berujung error 1064 karena klausanya wajib ada. Sifat per barisnya juga perlu
diperhitungkan, karena pernyataan yang menyentuh ribuan baris menjalankan
tubuh trigger sebanyak itu pula, sehingga logika yang berat di dalamnya
memperlambat seluruh pernyataan pemicunya. Kesalahan keempat adalah
membiarkan skrip tanpa DROP TRIGGER IF EXISTS, sehingga skrip hanya berjalan
sekali dan gagal dengan error 1359 pada eksekusi berikutnya.
Ringkasan#
- Trigger adalah objek bernama yang dijalankan otomatis untuk setiap baris
terdampak ketika peristiwa
INSERT,UPDATE, atauDELETEterjadi pada tabel pemicunya. - Kolom
NEWmemuat nilai yang akan disimpan dan hanya dapat ditulis pada triggerBEFORE, sedangkanOLDmemuat nilai sebelum perubahan dan hanya ada padaUPDATEsertaDELETE. - Pencatatan audit lazim memakai tiga trigger
AFTERyang menulis ke tabel log terpisah, dan baris lognya ikut ter-ROLLBACKapabila pernyataan pemicunya gagal. - Daftar trigger dapat dibaca dari
information_schema.triggers, dan polaDROP TRIGGER IF EXISTSmenjaga skrip tetap dapat dijalankan ulang.
Referensi#
- CREATE TRIGGER Statement — MySQL 8.0 Reference Manual
— Sintaks trigger, ketersediaan
OLDdanNEW, serta aturan penamaannya. - Trigger Syntax and Examples — MySQL 8.0 Reference Manual — Perilaku pemicuan per baris dan contoh pemakaian trigger.
- Stored Program Restrictions — MySQL 8.0 Reference Manual — Batasan trigger, termasuk larangan mengubah tabel yang sedang dipakai pernyataan pemicunya.
- The INFORMATION_SCHEMA TRIGGERS Table — MySQL 8.0 Reference Manual — Kolom kamus data untuk memeriksa trigger yang terdaftar.