Foreign Key di MariaDB - Biarkan Database Menolak Relasi yang Mustahil
Sebuah baris post menyatakan bahwa penulisnya adalah user 42, tetapi user 42 tidak ada. Aplikasi dapat menyembunyikan nama yang hilang, mengembalikan error, atau diam-diam menampilkan “penulis tidak dikenal”, tetapi tidak satu pun respons itu memperbaiki relasi yang tersimpan di database. Pertanyaan yang lebih sulit adalah di mana state yang mustahil itu seharusnya ditolak.
Validasi aplikasi berguna, tetapi foreign key menempatkan aturan tersebut pada batas data bersama. Setiap penulis data yang memakai database kemudian harus mematuhi relasi yang sama, baik penulisan itu berasal dari PHP, maintenance script, import, maupun aplikasi kedua. Jaminannya kuat, tetapi sempit: foreign key dapat membuktikan bahwa baris yang dirujuk ada. Foreign key tidak dapat membuktikan bahwa user saat ini boleh merujuknya, bahwa kontennya valid, atau bahwa menghapusnya adalah keputusan yang bijak.
Artikel ini membangun batas tersebut untuk aplikasi PHP kecil yang memakai MariaDB dan InnoDB. Pekerjaan pentingnya bukan sekadar menambahkan FOREIGN KEY. Kita perlu memutuskan arti relasinya, apa yang seharusnya terjadi ketika parent-nya hilang, dan bagaimana mulai menerapkan constraint tanpa mengabaikan data yang sudah tidak konsisten.
Constraint mendeskripsikan satu invariant
Misalkan sebuah aplikasi menyimpan author dan post. Tabel parent memiliki identifier author; tabel child menyimpan salah satu identifier tersebut di posts.author_id. Menurut dokumentasi foreign key MariaDB, setiap nilai child selain NULL harus cocok dengan sebuah nilai pada key parent yang dirujuk.
CREATE TABLE authors (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE posts (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
author_id BIGINT UNSIGNED NULL,
title VARCHAR(191) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_posts_author
FOREIGN KEY (author_id) REFERENCES authors (id)
ON DELETE SET NULL
ON UPDATE RESTRICT
) ENGINE=InnoDB;
Dengan definisi ini, insert yang memberikan author_id selain NULL tetapi tidak dikenal akan ditolak. MariaDB mendokumentasikan kasus itu sebagai error 1452 dengan SQLSTATE 23000. Constraint tersebut juga sengaja diberi nama. MariaDB dapat membuat nama secara otomatis, tetapi nama yang stabil membuat pesan error, pemeriksaan schema, dan migration berikutnya lebih mudah dipahami.
Contoh ini mengizinkan author_id bernilai NULL. Itu adalah pilihan data model, bukan kewajiban foreign key. Di sini artinya sebuah post boleh bertahan tanpa relasi author saat ini. Jika setiap post harus selalu memiliki author, kolom tersebut seharusnya NOT NULL dan kebijakan penghapusannya harus mempertahankan aturan itu.
Mengapa pemeriksaan keberadaan di PHP bukan jaminan yang sama
Sebuah handler dapat melakukan query ke authors sebelum memasukkan post. Langkah ini menghasilkan jalur validasi yang lebih ramah, tetapi tidak menggantikan constraint. Penulis data lain mungkin tidak melakukan pemeriksaan itu. Lebih halus lagi, author dapat dihapus setelah pemeriksaan dan sebelum insert. Kedua statement tersebut mengamati momen yang berbeda kecuali aplikasi mengoordinasikannya dengan aturan database dan perilaku transaction yang sesuai.
Foreign key mengevaluasi relasi di tempat write diterima. Validasi aplikasi masih dapat menjelaskan masalah sebelum mencoba write, sedangkan database tetap menjadi batas integritas terakhir. Alasannya sama seperti form yang boleh memeriksa apakah username tampak tersedia, sementara unique constraint yang menentukan apakah dua registrasi bersamaan benar-benar dapat mengklaimnya.
Itu bukan berarti setiap database error pantas menjadi pesan antarmuka. Nama constraint atau statement SQL mentah seharusnya masuk ke diagnostic yang terlindungi, bukan response publik. Aplikasi perlu menerjemahkan konflik yang sudah diperkirakan ke bahasa domainnya sendiri, seperti “Pilih author yang tersedia,” sedangkan kegagalan tak terduga mengikuti error handling server seperti biasa.
Aksi penghapusan adalah kebijakan, bukan tombol kemudahan
Baris yang paling berpengaruh sering kali adalah ON DELETE. MariaDB mendukung RESTRICT, CASCADE, dan SET NULL untuk foreign key InnoDB biasa. Di MariaDB, NO ACTION adalah sinonim RESTRICT, dan aksi yang tidak ditulis menggunakan RESTRICT secara default. Detail tersebut khusus untuk produk ini; database lain dapat memberikan semantik waktu yang berbeda pada NO ACTION.
RESTRICTmenolak penghapusan parent selama masih ada child yang merujuknya. Ini merupakan default yang berhati-hati ketika child memiliki riwayat sendiri atau penghapusan memerlukan workflow eksplisit.CASCADEmenghapus child yang cocok secara otomatis. Ini sesuai bagi baris yang benar-benar merupakan komponen parent, seperti item di dalam draft sementara yang tidak lagi bermakna ketika draft dihapus.SET NULLmempertahankan setiap child tetapi menghapus relasinya. Aksi ini memerlukan kolom child yang nullable dan interpretasi jujur atas “tidak ada parent saat ini.”
Dokumentasi constraint PostgreSQL menawarkan pembedaan desain umum yang berguna: cascading tepat ketika child adalah komponen yang tidak dapat berdiri sendiri secara masuk akal, sedangkan restriction lebih cocok ketika parent dan child mewakili objek independen. Itu adalah panduan konseptual, bukan dokumentasi perilaku MariaDB.
Untuk konten published, penghapusan otomatis bisa terlalu destruktif meskipun secara teknis valid. Mempertahankan post dengan author nullable, mencegah penghapusan author, atau memakai state deactivation pada level aplikasi masing-masing dapat lebih jujur. Cascade seharusnya mengodekan aturan lifecycle yang sudah diputuskan, bukan sekadar menghemat satu statement DELETE.
Kolom-kolomnya harus selaras
Sebuah relasi dapat benar secara konsep tetapi tetap gagal pada level schema. MariaDB mengharuskan parent dan child menggunakan storage engine yang sama dan didukung serta definisi kolom yang kompatibel. Untuk integer key, ukuran dan sign harus sama. Parent BIGINT UNSIGNED seharusnya tidak dipasangkan dengan child BIGINT signed. Untuk string key, character set dan collation juga harus sama.
Kolom parent yang dirujuk harus memiliki index, sedangkan kolom child memerlukan BTREE index atau bagian paling kiri dari index tersebut. InnoDB dapat membuat child index saat diperlukan, tetapi bergantung pada efek samping itu dapat menyembunyikan maksud index. Relasi composite memerlukan kolom dalam urutan yang benar. Prefix index, dan karena itu prefix TEXT atau BLOB biasa, tidak dapat dipakai sebagai kolom foreign key berdasarkan aturan MariaDB yang didokumentasikan.
Foreign key juga tidak menyiratkan NOT NULL. MariaDB mengizinkan baris child dengan nilai foreign key NULL. Schema harus memakai nullability untuk menyatakan apakah “tidak ada relasi” valid, bukan menganggap constraint menjawab pertanyaan tersebut.
Data yang sudah ada harus lolos sebelum aturan dapat membantu
Menambahkan constraint ke aplikasi yang sudah berjalan dimulai dari observasi. Pertama, periksa definisi tabel sebenarnya dengan SHOW CREATE TABLE, termasuk engine, tipe key, sign, character set, collation, dan index yang sudah ada. Kemudian cari baris child yang akan melanggar relasi yang diusulkan:
SELECT p.id, p.author_id
FROM posts AS p
LEFT JOIN authors AS a ON a.id = p.author_id
WHERE p.author_id IS NOT NULL
AND a.id IS NULL;
Hasil kosong mendukung penambahan constraint; hasil itu belum memilih aksi penghapusan yang tepat. Hasil yang tidak kosong adalah bukti bahwa aplikasi memerlukan keputusan data. Bergantung pada domainnya, perbaikan yang benar bisa berupa memulihkan parent yang hilang, memetakan child ke parent yang valid, mengubah reference opsional menjadi NULL, mengarantina baris untuk diperiksa, atau menghapus data yang memang terbukti tidak valid. Memilih salah satunya secara otomatis tanpa memahami record hanya membuat query menjadi sunyi.
Setelah data dan definisi selaras, migration dapat menambahkan constraint bernama:
ALTER TABLE posts
ADD CONSTRAINT fk_posts_author
FOREIGN KEY (author_id) REFERENCES authors (id)
ON DELETE SET NULL
ON UPDATE RESTRICT;
Ini adalah DDL production, bukan edit teks tanpa konsekuensi. Dokumentasi ALTER TABLE MariaDB menyatakan bahwa statement dapat menunggu metadata lock serta algorithm dan strategi locking yang tersedia bergantung pada operasi, engine, dan versi server. Pada tabel yang sibuk atau besar, periksa dokumentasi versi yang terpasang, uji dengan data representatif, rencanakan rollback, dan amati migration alih-alih menganggapnya akan berlangsung instan.
Jangan memakai pemeriksaan yang dimatikan sebagai strategi cleanup
MariaDB menyediakan foreign_key_checks, dan mematikannya dapat berguna dalam prosedur bulk loading atau schema yang terkontrol. Namun, itu juga menjadi cara mudah untuk membuat data yang biasanya ditolak oleh relasi yang telah dideklarasikan. Menyalakan pemeriksaan kembali bukan pengganti audit orphan, dan dokumentasi MariaDB memperingatkan bahwa menambahkan constraint ketika pemeriksaan dimatikan dapat membiarkan baris lama tidak tervalidasi.
Cerita migration yang lebih aman bersifat eksplisit: inventarisasi relasi, deteksi pelanggaran, putuskan bagaimana setiap kelas pelanggaran diperbaiki, jalankan DDL dalam operational window yang direncanakan, lalu verifikasi metadata hasilnya. Jika proses restore mengubah perilaku pemeriksaan untuk sementara, langkah validasinya harus menjadi bagian desain restore, bukan asumsi yang tidak tertulis.
Biarkan PHP menangani domain, bukan mengurai prosa database
PDO memakai exception mode secara default sejak PHP 8.0, menurut manual PHP mengenai PDO error handling. Sebuah PDOException menyediakan informasi SQLSTATE dan detail khusus driver. Struktur tersebut cukup bagi aplikasi untuk mencatat kegagalan dan, ketika operasi memiliki jalur konflik yang dikenal, mengembalikan response domain yang terkendali.
Hindari membangun perilaku inti dengan mencocokkan teks pesan error yang dibaca manusia. Susunan kata dalam pesan memuat detail engine dan dapat berubah. Bahkan SQLSTATE 23000 menjelaskan kelas integrity constraint, bukan satu kondisi bisnis, sehingga kode khusus driver atau pemeriksaan domain sebelumnya mungkin tetap diperlukan untuk membedakan parent yang tidak dikenal dari kegagalan constraint lain. Database tetap menjadi otoritas, sedangkan aplikasi tetap bertanggung jawab atas bahasa yang berguna, authorization, dan pengungkapan yang aman.
Transaction tetap penting ketika satu operasi mengubah beberapa baris. Foreign key mencegah reference yang rusak; foreign key tidak membuat workflow publish, billing, atau notification multi-langkah menjadi atomic. Foreign key juga tidak dapat menegakkan relasi ke objek yang hanya ada di service lain. Batas tersebut memerlukan protocol, reconciliation, atau state model tersendiri.
Verifikasi relasi yang dideklarasikan
Setelah deployment, verifikasi lebih dari satu insert yang berhasil. Test plan ringkas mencakup:
- Masukkan child dengan parent valid dan pastikan berhasil.
- Masukkan atau perbarui child dengan parent yang tidak dikenal dan pastikan ditolak.
- Jalankan perilaku penghapusan parent yang dipilih dan periksa setiap tabel yang terdampak.
- Pastikan
NULLditerima atau ditolak sesuai kebutuhan model. - Uji jalur aplikasi bersamaan yang membuat atau menghapus record terkait.
- Periksa definisi yang terpasang dengan
SHOW CREATE TABLE. - Lakukan query ke
information_schema.KEY_COLUMN_USAGEuntuk memastikan kolom yang diberi constraint dan kolom yang dirujuk. - Pastikan error publik tidak membocorkan SQL, nama constraint, atau detail record privat.
Pengujian juga perlu mencakup pemulihan backup dan schema migration. Constraint yang hanya ada di database live tetapi tidak ada dalam migration file atau backup terverifikasi adalah kecelakaan operasional yang menunggu untuk ditemukan kembali.
Kesimpulan
Foreign key MariaDB memberikan satu janji berharga kepada aplikasi PHP kecil: reference selain NULL tidak dapat menunjuk ke baris parent yang tidak dapat ditemukan database. Menempatkan janji itu di InnoDB melindungi relasi dari setiap penulis data, termasuk maintenance script masa depan yang mungkin lupa melakukan validasi.
Keyword adalah bagian yang mudah. Desain yang tahan lama lahir dari menyelaraskan definisi kolom, menentukan apakah ketiadaan diperbolehkan, memilih perilaku penghapusan berdasarkan lifecycle data, membersihkan orphan lama, dan memperlakukan ALTER TABLE sebagai perubahan operasional. Foreign key tidak menggantikan validasi aplikasi, authorization, transaction, atau keputusan penghapusan yang matang. Foreign key membuat satu invariant menjadi eksplisit dan dapat ditegakkan. Justru klaim yang lebih sempit itulah yang membuatnya berguna.
References
- MariaDB Documentation. Foreign Keys. Diakses 12 Oktober 2026.
- MariaDB Documentation.
ALTER TABLE. Diakses 12 Oktober 2026. - MariaDB Documentation. Information Schema
KEY_COLUMN_USAGETable. Diakses 12 Oktober 2026. - PHP Documentation Group. PDO: Errors and error handling. Diakses 12 Oktober 2026.
- PostgreSQL Global Development Group. PostgreSQL 18 Documentation: Foreign Keys. Diakses 12 Oktober 2026.
