Peta privilege dari chapter sebelumnya baru menjadi kekuatan nyata ketika kita bisa memberi dan mencabutnya dengan cermat. GRANT adalah pemberi hak — dari satu hak baca di satu tabel sampai seluruh kombinasi CRUD di satu database. REVOKE adalah pencabutnya, dan keduanya bekerja pada tabel grant yang sama dengan SHOW GRANTS. Di antara keduanya tersebar jebakan yang nyata: memberi hak ke akun yang belum dibuat meledak dengan ERROR 1410, mencabut hak yang tidak pernah diberikan ditolak dengan ERROR 1147, dan satu klausa kecil bernama WITH GRANT OPTION bisa mengubah user biasa menjadi “admin mini” yang diam-diam membagikan hak ke orang lain. Pada chapter ini kita praktikkan semuanya satu per satu, ditutup pertanyaan klasik: apakah kita perlu FLUSH PRIVILEGES setelah GRANT?

Menyiapkan Latihan #

Skenario kita sama seperti sebelumnya: database belajar_aman dengan tabel produk, dan satu akun aplikasi aplikasi_toko yang masih polos tanpa hak apa pun:

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

CREATE TABLE produk (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nama VARCHAR(60) NOT NULL,
  kategori VARCHAR(30) NOT NULL,
  harga DECIMAL(10,2) NOT NULL,
  stok INT NOT NULL
);

INSERT INTO produk (nama, kategori, harga, stok) VALUES
  ('Kopi Arabika Gayo', 'Minuman', 85000.00, 40),
  ('Teh Melati Premium', 'Minuman', 45000.00, 120),
  ('Gula Aren Cair', 'Bahan', 32000.00, 75),
  ('Susu Kental Manis', 'Bahan', 18000.00, 200),
  ('Sirup Jeruk', 'Minuman', 27000.00, 0);

DROP USER IF EXISTS 'aplikasi_toko'@'localhost';
CREATE USER 'aplikasi_toko'@'localhost' IDENTIFIED BY 'SandiKuat2026!';

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, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

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

Query OK, 0 rows affected (0.00 sec)

GRANT: Memberi Hak dengan Cermat #

Bentuk dasarnya satu baris: daftar privilege, klausa ON yang menentukan cakupan, dan akun penerima. Kita mulai dari hak baca yang paling sempit — satu tabel:

sql
GRANT SELECT ON belajar_aman.produk TO 'aplikasi_toko'@'localhost';

SHOW GRANTS FOR 'aplikasi_toko'@'localhost';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

+------------------------------------------------------------------------+
| Grants for aplikasi_toko@localhost                                     |
+------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `aplikasi_toko`@`localhost`                      |
| GRANT SELECT ON `belajar_aman`.`produk` TO `aplikasi_toko`@`localhost` |
+------------------------------------------------------------------------+
2 rows in set (0.00 sec)

Query OK menandakan hak tercatat, dan SHOW GRANTS memperlihatkan baris GRANT SELECT ON belajar_aman.produk`` menempel di bawah GRANT USAGE — tanda akun tanpa privilege global. Selanjutnya kita tambahkan tiga hak tulis sekaligus, tetapi kali ini pada tingkat database:

sql
GRANT INSERT, UPDATE, DELETE ON belajar_aman.* TO 'aplikasi_toko'@'localhost';

SHOW GRANTS FOR 'aplikasi_toko'@'localhost';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

+---------------------------------------------------------------------------------+
| Grants for aplikasi_toko@localhost                                              |
+---------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `aplikasi_toko`@`localhost`                               |
| GRANT INSERT, UPDATE, DELETE ON `belajar_aman`.* TO `aplikasi_toko`@`localhost` |
| GRANT SELECT ON `belajar_aman`.`produk` TO `aplikasi_toko`@`localhost`          |
+---------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

Satu statement, tiga privilege, satu tingkat cakupan — hasilnya baris baru GRANT INSERT, UPDATE, DELETE ON belajar_aman.* yang terpisah dari hak tingkat tabel. Server memang menyimpan grant per tingkat: baris belajar_aman.produk di mysql.tables_priv, baris belajar_aman di mysql.db. Pola yang sama berlaku untuk menaikkan cakupan ke *.* (global) — cukup ganti objek pada klausa ON.

Memberi Hak ke Akun yang Belum Ada #

Versi MySQL lama diam-diam membuat akun baru ketika GRANT ditujukan ke akun yang tidak ada. Perilaku itu sudah lama dihapus demi keamanan — dan error berikut adalah buktinya:

sql
-- @error
GRANT SELECT ON belajar_aman.produk TO 'hantu'@'localhost';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1410 (42000): You are not allowed to create a user with GRANT
Bye

Server menolak dengan ERROR 1410 (42000): You are not allowed to create a user with GRANT — hak tidak diberikan, akun tidak dibuat, tidak ada efek samping. Urutan yang benar selalu: CREATE USER lebih dulu, baru GRANT. Bila error ini muncul di skrip migrasi, hampir pasti ada langkah pembuatan akun yang terlewat atau salah ketik nama akunnya.

REVOKE: Mencabut Hak yang Terdaftar #

REVOKE adalah cermin dari GRANT — sintaksisnya nyaris identik, hanya TO menjadi FROM. Kita cabut hak INSERT milik aplikasi_toko:

sql
REVOKE INSERT ON belajar_aman.* FROM 'aplikasi_toko'@'localhost';

SHOW GRANTS FOR 'aplikasi_toko'@'localhost';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

+-------------------------------------------------------------------------+
| Grants for aplikasi_toko@localhost                                      |
+-------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `aplikasi_toko`@`localhost`                       |
| GRANT UPDATE, DELETE ON `belajar_aman`.* TO `aplikasi_toko`@`localhost` |
| GRANT SELECT ON `belajar_aman`.`produk` TO `aplikasi_toko`@`localhost`  |
+-------------------------------------------------------------------------+
3 rows in set (0.00 sec)

Baris GRANT INSERT, UPDATE, DELETE berubah menjadi GRANT UPDATE, DELETE — hak baca tingkat tabel tidak tersentuh karena cakupannya berbeda. Mencabut juga bisa banyak sekaligus (REVOKE UPDATE, DELETE ON belajar_aman.* FROM ...) dan bahkan seluruh level sekaligus, seperti kita lakukan sebentar lagi.

Mencabut Hak yang Tidak Pernah Diberikan #

Kebalikan error 1410 terjadi di REVOKE: mencabut sesuatu yang memang tidak tercatat di level itu ditolak dengan keras. Kita uji dengan DROP pada tingkat tabel — hak yang tidak pernah kita berikan:

sql
-- @error
REVOKE DROP ON belajar_aman.produk FROM 'aplikasi_toko'@'localhost';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

ERROR 1147 (42000): There is no such grant defined for user 'aplikasi_toko' on host 'localhost' on table 'produk'
Bye

Server menjawab ERROR 1147 (42000): There is no such grant defined for user 'aplikasi_toko' on host 'localhost' on table 'produk' — pesan yang jujur: tidak ada baris grant tingkat tabel untuk kombinasi akun dan tabel itu… meskipun akunnya sendiri punya hak tingkat database. Pelajarannya: cakupan harus cocok persis. Hak yang diberikan di belajar_aman.* harus dicabut di belajar_aman.* juga; mencabutnya di belajar_aman.produk tidak “menyerap” hak dari tingkat atas.

GRANT ALL dan WITH GRANT OPTION #

Pintasan ALL memberikan seluruh privilege pada cakupan yang ditulis — kita lihat wujudnya sebelum membahas risikonya:

sql
GRANT ALL ON belajar_aman.* TO 'aplikasi_toko'@'localhost';

SHOW GRANTS FOR 'aplikasi_toko'@'localhost';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

+-------------------------------------------------------------------------+
| Grants for aplikasi_toko@localhost                                      |
+-------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `aplikasi_toko`@`localhost`                       |
| GRANT ALL PRIVILEGES ON `belajar_aman`.* TO `aplikasi_toko`@`localhost` |
| GRANT SELECT ON `belajar_aman`.`produk` TO `aplikasi_toko`@`localhost`  |
+-------------------------------------------------------------------------+
3 rows in set (0.00 sec)

Baris GRANT ALL PRIVILEGES ON belajar_aman.* kini memuat segalanya di database itu: SELECT, tulis, CREATE, DROP, ALTER, semuanya. Berbeda dengan dua jebakan sebelumnya, GRANT ALL tidak error — justru karena itulah ia berbahaya: pemberian yang terasa “hemat satu kata” ini membuka kemampuan menjatuhkan seluruh tabel aplikasi sendiri.

Sebagai penutup rangkaian ini, kita bersihkan akun kembali ke titik nol dan beri satu hak dengan klausa paling berbahaya:

sql
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'aplikasi_toko'@'localhost';

GRANT SELECT ON belajar_aman.produk TO 'aplikasi_toko'@'localhost' WITH GRANT OPTION;

SHOW GRANTS FOR 'aplikasi_toko'@'localhost';

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+------------------------------------------------------------------------------------------+
| Grants for aplikasi_toko@localhost                                                       |
+------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `aplikasi_toko`@`localhost`                                        |
| GRANT SELECT ON `belajar_aman`.`produk` TO `aplikasi_toko`@`localhost` WITH GRANT OPTION |
+------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

Dua hal mari kita bedah. Statement REVOKE ALL PRIVILEGES, GRANT OPTION FROM ... (tanpa klausa ON!) adalah cara resmi menghapus seluruh privilege pada seluruh tingkat sekaligus — kembali ke GRANT USAGE polos. Setelah itu, GRANT SELECT ... WITH GRANT OPTION memberi hak baca plus satu kekuasaan tambahan yang terlihat jelas di akhir baris SHOW GRANTS: aplikasi_toko kini boleh menjalankan GRANT SELECT ON belajar_aman.produk kepada akun lain. Hak berbagi hak semacam ini membuat rantai pemberian sulit diaudit — privilege menyebar lewat jalur yang tidak melalui administrator.

FLUSH PRIVILEGES: Kapan Sebenarnya Perlu #

Pertanyaan lama ini punya jawaban pendek di MySQL versi 8: hampir tidak pernah. GRANT, REVOKE, CREATE USER, dan DROP USER semuanya menulis ke tabel grant dan sekaligus menyegarkan salinan di memori server — tidak ada langkah tambahan:

sql
FLUSH PRIVILEGES;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Statement di atas tetap sah dan berhasil — tetapi di skenario kita ia tidak mengubah apa pun. FLUSH PRIVILEGES baru benar-benar dibutuhkan ketika tabel grant dimodifikasi langsung lewat INSERT/UPDATE/DELETE pada mysql.user, mysql.db, dan kawan-kawannya — jalur yang dulu umum dipakai skrip lama. Memodifikasi tabel sistem secara manual adalah praktik berisiko tinggi; jalur resmi (GRANT/REVOKE/CREATE USER) sudah menangani semuanya.

Membersihkan Latihan #

Akun latihan beserta seluruh grant-nya kita jatuhkan — DROP USER menghapus baris grant di semua tabel sistem sekaligus:

sql
DROP USER IF EXISTS 'aplikasi_toko'@'localhost';

DROP DATABASE IF EXISTS belajar_aman;

Output yang Dihasilkan:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Referensi #

Kita kini menguasai siklus pemberian dan pencabutan hak lengkap dengan jebakan-jebakan error-nya. Tetapi bayangkan mengelola grant untuk dua puluh anggota tim dengan peran yang sama — dua puluh kali GRANT yang harus identik. MySQL punya alat khusus untuk pola ini: role, wadah privilege yang bisa dibagikan dan diaktifkan per sesi, yang kita bahas tuntas di chapter berikutnya.