Trigger hanya menyala ketika ada statement DML yang datang — padahal banyak pekerjaan database justru harus berjalan ketika tidak ada siapa pun yang mengetik apa pun: rekap stok setiap tengah malam, pengarsipan log setiap minggu, atau pemangkasan tabel sementara setiap jam. MySQL menjawab kebutuhan ini dengan objek bernama event: satu blok SQL yang disimpan di database lengkap dengan jadwalnya sendiri, dieksekusi oleh scheduler internal server. Pada chapter ini kita memeriksa kondisi scheduler, membuat event berkala dan event sekali jalan, mengelola statusnya, lalu mempelajari kebiasaan penting saat menguji event tanpa menunggu jamnya tiba.

Event: Tugas Terjadwal Milik Server #

Sebuah event pada dasarnya adalah pasangan “jadwal + statement”. Kita mendaftarkan kapan pekerjaan harus berjalan (ON SCHEDULE) dan apa yang harus dikerjakan (DO), lalu server menyimpannya sebagai objek database — persis seperti tabel atau trigger. Scheduler internal, sebuah thread khusus di dalam server, terus memantau daftar event dan mengeksekusi statementnya begitu waktunya tiba. Karena scheduler hidup di dalam server, pekerjaan tetap berjalan meskipun tidak ada aplikasi yang terhubung; sebaliknya, pekerjaan juga tidak berjalan ketika server mati — sifat yang kita bahas di chapter keterbatasan.

Menyiapkan Tabel Latihan #

Kita pakai database belajar_trig dengan tabel produk dan tabel log_stok sebagai target tulisan event. Tabel log_stok sengaja tanpa kolom waktu otomatis supaya baris contoh mudah diperiksa:

sql
DROP DATABASE IF EXISTS belajar_trig;
CREATE DATABASE belajar_trig;
USE belajar_trig;

CREATE TABLE produk (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(100) NOT NULL,
  harga DECIMAL(10,2) NOT NULL,
  stok INT NOT NULL DEFAULT 0
);

CREATE TABLE log_stok (
  id INT AUTO_INCREMENT PRIMARY KEY,
  pesan VARCHAR(100) NOT NULL
);

INSERT INTO produk (nama, harga, stok) VALUES
  ('Kopi Gayo', 55000.00, 10),
  ('Teh Melati', 25000.00, 5);

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

Memeriksa Kondisi Scheduler #

Seluruh event hanya berjalan bila scheduler menyala, jadi pemeriksaan kondisi selalu menjadi langkah pertama. Statusnya tersimpan di variabel sistem event_scheduler:

sql
SELECT @@event_scheduler AS status_scheduler;

Output yang Dihasilkan:

text
+------------------+
| status_scheduler |
+------------------+
| ON               |
+------------------+
1 row in set (0.00 sec)

Pada server uji kita nilainya ON: sejak MySQL 8.4, event scheduler menyala secara bawaan, mengubah kebiasaan lama dari MySQL 8.0 ke bawah yang mengirim OFF sebagai bawaan. Nilai ini bukan konstanta — administrator bisa mematikan atau menyalakannya kapan saja. Kita coba matikan sejenak untuk melihat dua sisi nilainya:

sql
SET GLOBAL event_scheduler = OFF;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)
sql
SELECT @@event_scheduler AS status_scheduler;

Output yang Dihasilkan:

text
+------------------+
| status_scheduler |
+------------------+
| OFF              |
+------------------+
1 row in set (0.00 sec)

Perintah SET GLOBAL baru efektif pada sesi-sesi berikutnya, dan pemeriksaan di blok terpisah membuktikan nilainya kini OFF. Selama kondisi ini, seluruh event di server berhenti tuntas — jadwal tidak dieksekusi, event baru tetap bisa dibuat tetapi hanya menunggu. Mengubah nilai ini membutuhkan hak administratif (SYSTEM_VARIABLES_ADMIN atau hak SUPER lama); akun root yang kita pakai memiliki keduanya. Kita nyalakan kembali untuk latihan berikutnya:

sql
SET GLOBAL event_scheduler = ON;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Membuat Event Berkala #

Event berkala memakai klausa EVERY untuk interval dan STARTS untuk waktu mulai. Event berikut mencatat rekap total stok ke log_stok setiap satu menit:

sql
DROP EVENT IF EXISTS catat_total_stok;

CREATE EVENT catat_total_stok
ON SCHEDULE EVERY 1 MINUTE
STARTS '2030-01-01 00:00:00'
DO INSERT INTO log_stok (pesan) SELECT CONCAT('total stok: ', SUM(stok), ' unit') FROM produk;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Baris DROP EVENT IF EXISTS membersihkan event bernama sama dari percobaan sebelumnya — pola idempoten yang sudah kita pakai untuk trigger. Bagian ON SCHEDULE EVERY 1 MINUTE menentukan interval berulang; STARTS '2030-01-01 00:00:00' menentukan eksekusi pertama. Statement setelah DO adalah isi pekerjaan event: INSERT INTO log_stok ... SELECT yang merangkum seluruh stok menjadi satu baris pesan. Event memanggil statement INSERT ... SELECT biasa tanpa kejutan — isi event boleh berupa statement SQL tunggal apa pun, atau blok BEGIN ... END dengan DELIMITER bila pekerjaannya berlapis.

Membaca Jadwal dan Status Event #

Pendaftaran event terbukti lewat metadata. SHOW EVENTS menampilkan seluruh event milik database aktif:

sql
SHOW EVENTS;

Output yang Dihasilkan:

text
+--------------+------------------+----------------+-----------+-----------+------------+----------------+----------------+---------------------+------+---------+------------+----------------------+----------------------+--------------------+
| Db           | Name             | Definer        | Time zone | Type      | Execute at | Interval value | Interval field | Starts              | Ends | Status  | Originator | character_set_client | collation_connection | Database Collation |
+--------------+------------------+----------------+-----------+-----------+------------+----------------+----------------+---------------------+------+---------+------------+----------------------+----------------------+--------------------+
| belajar_trig | catat_total_stok | root@localhost | SYSTEM    | RECURRING | NULL       | 1              | MINUTE         | 2030-01-01 00:00:00 | NULL | ENABLED |          1 | utf8mb4              | utf8mb4_0900_ai_ci   | utf8mb4_0900_ai_ci |
+--------------+------------------+----------------+-----------+-----------+------------+----------------+----------------+---------------------+------+---------+------------+----------------------+----------------------+--------------------+
1 row in set (0.00 sec)

Satu baris hasil merangkum identitas event: kolom Type bernilai RECURRING karena jadwalnya berulang, Interval value dan Interval field menunjukkan 1 MINUTE, kolom Starts menampilkan literal 2030-01-01 00:00:00 yang kita daftarkan, dan Status bernilai ENABLED. Query berbasis katalog memberikan kontrol kolom yang lebih rapi:

sql
SELECT EVENT_NAME, EVENT_TYPE, INTERVAL_VALUE, INTERVAL_FIELD, STATUS, EVENT_DEFINITION
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = 'belajar_trig';

Output yang Dihasilkan:

text
+------------------+------------+----------------+----------------+---------+--------------------------------------------------------------------------------------------+
| EVENT_NAME       | EVENT_TYPE | INTERVAL_VALUE | INTERVAL_FIELD | STATUS  | EVENT_DEFINITION                                                                           |
+------------------+------------+----------------+----------------+---------+--------------------------------------------------------------------------------------------+
| catat_total_stok | RECURRING  | 1              | MINUTE         | ENABLED | INSERT INTO log_stok (pesan) SELECT CONCAT('total stok: ', SUM(stok), ' unit') FROM produk |
+------------------+------------+----------------+----------------+---------+--------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

Kolom EVENT_DEFINITION memperlihatkan statement DO persis seperti yang kita tulis — metadata ini menjadi alat diagnosis ketika event tidak berperilaku seperti dugaan. Kolom lain seperti LAST_EXECUTED dan CREATED sengaja tidak kita tampilkan karena berisi waktu nyata yang berubah setiap eksekusi.

Menguji Tanpa Menunggu #

Berapa baris yang sudah ditulis event kita? Jawabannya nol, dan itu memang kesimpulan yang benar:

sql
SELECT COUNT(*) AS baris_log FROM log_stok;

Output yang Dihasilkan:

text
+-----------+
| baris_log |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

Event belum pernah menyala karena STARTS kita daftarkan pada tahun 2030 — jauh di masa depan. Inilah kebiasaan pengujian yang penting: latihan tidak perlu menunggu interval event tiba. Kita membuktikan pendaftaran lewat SHOW EVENTS dan information_schema, bukan lewat efek eksekusinya; menunggu satu menit penuh demi satu baris log hanya memperlambat siklus uji. Saat menguji di mesin sendiri, interval pendek semacam EVERY 5 SECOND dengan STARTS beberapa detik ke depan memang membuat event terlihat hidup — teknik yang sah untuk percobaan lokal, tetapi jangan ditunggu di dalam latihan tertulis seperti ini.

Event Sekali Jalan dengan AT #

Jadwal kedua jenisnya AT: pekerjaan dieksekusi tepat satu kali pada satu waktu tertentu, lalu selesai. Pola ini cocok untuk tugas tertanggal seperti pengarsipan tahunan:

sql
DROP EVENT IF EXISTS arsipkan_satu_kali;

CREATE EVENT arsipkan_satu_kali
ON SCHEDULE AT '2030-06-01 02:00:00'
DO INSERT INTO log_stok (pesan) VALUES ('arsip tahunan');

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Perbedaan metadata kedua jenis jadwal langsung terlihat di katalog:

sql
SELECT EVENT_NAME, EVENT_TYPE, EXECUTE_AT, STATUS
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = 'belajar_trig'
ORDER BY EVENT_NAME;

Output yang Dihasilkan:

text
+--------------------+------------+---------------------+---------+
| EVENT_NAME         | EVENT_TYPE | EXECUTE_AT          | STATUS  |
+--------------------+------------+---------------------+---------+
| arsipkan_satu_kali | ONE TIME   | 2030-06-01 02:00:00 | ENABLED |
| catat_total_stok   | RECURRING  | NULL                | ENABLED |
+--------------------+------------+---------------------+---------+
2 rows in set (0.00 sec)

Event arsipkan_satu_kali bertipe ONE TIME dengan kolom EXECUTE_AT berisi literal waktunya, sedangkan catat_total_stok bertipe RECURRING dengan EXECUTE_AT kosong (NULL) — jadwalnya dijelaskan interval, bukan satu tanggal. Default ON COMPLETION NOT PRESERVE menghapus event sekali jalan begitu waktunya lewat; klausa ON COMPLETION PRESERVE membuat definisinya tetap tersimpan sebagai arsip.

Menonaktifkan Tanpa Menghapus #

Event yang tidak ingin berjalan sementara bisa dijeda tanpa dihapus:

sql
ALTER EVENT catat_total_stok DISABLE;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)
sql
SELECT EVENT_NAME, STATUS
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = 'belajar_trig'
ORDER BY EVENT_NAME;

Output yang Dihasilkan:

text
+--------------------+----------+
| EVENT_NAME         | STATUS   |
+--------------------+----------+
| arsipkan_satu_kali | ENABLED  |
| catat_total_stok   | DISABLED |
+--------------------+----------+
2 rows in set (0.00 sec)

Status catat_total_stok berubah menjadi DISABLED sementara arsipkan_satu_kali tetap ENABLED — penonaktifan berlaku per event, bukan untuk seluruh scheduler. Scheduler pun melompati event berstatus DISABLED seolah jadwalnya tidak ada, dan ALTER EVENT ... ENABLE menghidupkannya kembali tanpa mendefinisikan ulang. Membuat event langsung dalam keadaan mati juga bisa: tambahkan klausa DISABLE di akhir CREATE EVENT.

Menghapus Event #

Pembersihan event memakai DROP EVENT, dan jumlah baris katalog menjadi buktinya:

sql
DROP EVENT catat_total_stok;
DROP EVENT arsipkan_satu_kali;

SELECT COUNT(*) AS jumlah_event
FROM information_schema.EVENTS
WHERE EVENT_SCHEMA = 'belajar_trig';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+--------------+
| jumlah_event |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

Daftar event kembali kosong. Perlu dicatat bahwa DROP DATABASE juga ikut menjatuhkan seluruh event di dalamnya — event adalah milik schema, sama seperti tabel dan trigger — namun membersihkan secara eksplisit tetap lebih rapi ketika event sengaja dipensiunkan lebih dulu.

Dua pembersihan terakhir berjalan bersama: scheduler dikembalikan ke nilai awal ON yang kita catat di awal chapter, dan database latihan beserta seluruh objeknya dijatuhkan:

sql
SET GLOBAL event_scheduler = ON;

DROP DATABASE belajar_trig;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 2 rows affected (0.00 sec)

Referensi #

Pekerjaan terjadwal kini punya rumah: event menyimpan jadwal dan isinya di dalam server, scheduler mengeksekusinya tepat waktu, dan metadata memungkinkan kita memeriksanya tanpa menunggu. Trigger dan event sama-sama otomatis, tetapi keduanya bukan tanpa batas — ada larangan keras dari MySQL, ada biaya per baris, dan ada skenario ketika solusi sederhana justru lebih tepat. Chapter penutup section ini merangkum seluruh batasan tersebut beserta panduan kapan memilih jalan lain.