Perbedaan Primary Key dan Foreign Key di MySQL: Panduan Lengkap

| | | MySQL (My Structured Query Language)
Perbedaan Primary Key dan Foreign Key di MySQL: Panduan Lengkap

primary key dan foreign key adalah dua konsep yang hampir selalu muncul barengan begitu kita bicara database relasional. kalau primary key itu identitas, foreign key itu penghubungnya. tanpa keduanya, tabel cuma jadi kumpulan data yang tidak saling kenal.

dulu saya sempat menganggap foreign key cuma formalitas. bikin tabel, relasi dijaga lewat kode Laravel saja. sampai suatu hari laporan keuangan menunjukkan transaksi milik mahasiswa dengan NIM yang tidak ada di tabel mahasiswa. data yatim. sejak itu saya tidak pernah merancang tabel tanpa foreign key lagi. artikel ini saya tulis supaya kalian tidak perlu mengalami hal yang sama.

ringkasan cepat: primary key = identitas unik tiap baris, satu per tabel, tidak boleh NULL. foreign key = kolom yang merujuk ke primary key di tabel lain, boleh duplikat dan boleh NULL, tujuannya menjaga integritas data antar tabel.

1. Apa Itu Primary Key?

primary key (kunci utama) adalah kolom atau kombinasi kolom yang secara unik mengidentifikasi setiap baris dalam tabel. di InnoDB, primary key sekaligus menentukan urutan penyimpanan fisik data (clustered index), jadi pemilihan primary key ngaruh langsung ke performa.

Karakteristik primary key

  • unik — tidak boleh ada nilai yang sama
  • NOT NULL — wajib diisi
  • hanya satu per tabel — tapi bisa composite (gabungan beberapa kolom)
  • otomatis jadi clustered index di InnoDB, jadi pencarian berdasarkan primary key itu paling cepat
  • tipe stabil — jangan pakai kolom yang sering berubah seperti nama atau alamat

Contoh paling umum: surrogate key

CREATE TABLE mahasiswa (
    nim VARCHAR(10) PRIMARY KEY,
    nama VARCHAR(100) NOT NULL,
    jurusan VARCHAR(50)
) ENGINE=InnoDB;

INSERT INTO mahasiswa VALUES 
('G64180001', 'Andi Pratama', 'Ilmu Komputer'),
('G64180002', 'Budi Santoso', 'Sistem Informasi');

di contoh ini nim adalah natural key (kunci alami dari dunia nyata). untuk tabel yang tidak punya natural key yang stabil, kita biasa pakai surrogate key:

CREATE TABLE users (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
) ENGINE=InnoDB;

BIGINT UNSIGNED AUTO_INCREMENT adalah standar paling aman di 2026. INT biasa maksimal 2,1 miliar, sementara BIGINT UNSIGNED sampai 18 quintillion, jadi tidak akan habis untuk aplikasi jangka panjang.

Composite primary key

kalau satu kolom tidak cukup untuk unik, gabungkan dua kolom:

CREATE TABLE nilai (
    nim VARCHAR(10) NOT NULL,
    kode_mk VARCHAR(10) NOT NULL,
    nilai INT,
    PRIMARY KEY (nim, kode_mk)
) ENGINE=InnoDB;

artinya kombinasi nim + kode_mk harus unik. satu mahasiswa tidak boleh punya dua nilai untuk mata kuliah yang sama. ini pola klasik untuk tabel pivot.

2. Apa Itu Foreign Key?

foreign key adalah kolom di tabel anak yang merujuk ke primary key (atau unique key) di tabel induk. tugasnya cuma satu: menjaga referential integrity, memastikan setiap nilai di kolom anak memang ada di tabel induk.

Karakteristik foreign key

  • boleh NULL jika relasinya opsional
  • boleh duplikat — inilah yang bikin relasi satu-ke-banyak (one-to-many) bisa terjadi
  • boleh banyak per tabel — satu tabel bisa punya beberapa foreign key ke tabel berbeda
  • harus tipe data identik dengan kolom yang dirujuk (INT ke INT, BIGINT ke BIGINT, panjang dan charset VARCHAR harus sama)
  • hanya jalan di InnoDB — MyISAM akan mengabaikan definisi foreign key tanpa error yang jelas
  • butuh index — MySQL otomatis bikinkan index jika belum ada, tapi lebih baik definisikan eksplisit

Contoh dasar

CREATE TABLE transaksi (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    nim VARCHAR(10),
    total DECIMAL(12,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (nim) REFERENCES mahasiswa(nim)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
) ENGINE=InnoDB;

-- berhasil, karena G64180001 ada di mahasiswa
INSERT INTO transaksi (nim, total) VALUES ('G64180001', 500000);

-- gagal, karena G64180099 tidak ada di mahasiswa
-- ERROR 1452: Cannot add or update a child row: a foreign key constraint fails
INSERT INTO transaksi (nim, total) VALUES ('G64180099', 300000);

tanpa foreign key, insert kedua akan lolos dan jadi data yatim. dengan foreign key, MySQL langsung menolak.

Cara menambah foreign key ke tabel yang sudah ada

-- pastikan kolom sudah di-index dan tipenya sama
ALTER TABLE transaksi
    ADD CONSTRAINT fk_transaksi_mahasiswa
    FOREIGN KEY (nim) REFERENCES mahasiswa(nim)
    ON DELETE RESTRICT ON UPDATE CASCADE;

-- cek foreign key yang sudah ada
SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL;

-- hapus jika salah
ALTER TABLE transaksi DROP FOREIGN KEY fk_transaksi_mahasiswa;

di Laravel 11/12, syntax modernnya lebih ringkas:

Schema::create('transaksi', function (Blueprint $table) {
    $table->id();
    $table->string('nim', 10)->nullable();
    $table->decimal('total', 12, 2);
    $table->timestamps();

    $table->foreign('nim')->references('nim')->on('mahasiswa')
          ->restrictOnDelete()->cascadeOnUpdate();
    // atau cara pendek:
    // $table->foreignId('user_id')->constrained()->restrictOnDelete();
});

3. Tabel Perbandingan Lengkap

AspekPrimary KeyForeign Key
Tujuanidentitas unik tiap barismenghubungkan tabel
Keunikanwajib unikboleh duplikat
NULLtidak bolehboleh (jika relasi opsional)
Jumlah per tabelmaksimal 1 (bisa composite)boleh banyak
Indexotomatis clustered indexotomatis dibuatkan index jika belum ada, tapi sebaiknya buat eksplisit
Wajib ada?sangat disarankan, hampir wajibopsional tapi best practice
Merujuk tabel lain?tidakya, ke PK/UNIQUE di tabel induk
Contohid, nim, kode_mktransaksi.nim, orders.user_id

kalau masih bingung, ingat analoginya: primary key itu KTP, satu orang satu nomor. foreign key itu kolom “nama ayah” di kartu keluarga yang merujuk ke KTP ayah, banyak anak bisa merujuk ke ayah yang sama.

4. Contoh Relasi Lengkap 4 Tabel

ini contoh yang saya pakai di kelas database, sudah mencakup one-to-many dan many-to-many:

-- Tabel induk 1
CREATE TABLE jurusan (
    id_jurusan INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    nama_jurusan VARCHAR(50) NOT NULL UNIQUE
) ENGINE=InnoDB;

-- Tabel induk 2, FK ke jurusan (one-to-many)
CREATE TABLE mahasiswa (
    nim VARCHAR(10) PRIMARY KEY,
    nama VARCHAR(100) NOT NULL,
    id_jurusan INT UNSIGNED,
    FOREIGN KEY (id_jurusan) REFERENCES jurusan(id_jurusan)
        ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB;

-- Tabel induk 3
CREATE TABLE mata_kuliah (
    kode_mk VARCHAR(10) PRIMARY KEY,
    nama_mk VARCHAR(100) NOT NULL,
    sks TINYINT UNSIGNED
) ENGINE=InnoDB;

-- Tabel pivot, composite PK + dua FK (many-to-many)
CREATE TABLE nilai (
    nim VARCHAR(10) NOT NULL,
    kode_mk VARCHAR(10) NOT NULL,
    nilai TINYINT UNSIGNED,
    PRIMARY KEY (nim, kode_mk),
    FOREIGN KEY (nim) REFERENCES mahasiswa(nim) ON DELETE CASCADE,
    FOREIGN KEY (kode_mk) REFERENCES mata_kuliah(kode_mk) ON DELETE RESTRICT
) ENGINE=InnoDB;

Diagram relasi

jurusan 1──< mahasiswa 1──< nilai >──1 mata_kuliah
  (satu jurusan punya banyak mahasiswa, satu mahasiswa punya banyak nilai)

Query JOIN dengan PK dan FK

-- transaksi lengkap dengan nama mahasiswa
SELECT t.id, m.nama, m.nim, t.total
FROM transaksi t
JOIN mahasiswa m ON t.nim = m.nim;

-- rekap nilai dengan nama mahasiswa dan mata kuliah
SELECT m.nama, mk.nama_mk, n.nilai
FROM nilai n
JOIN mahasiswa m ON n.nim = m.nim
JOIN mata_kuliah mk ON n.kode_mk = mk.kode_mk
WHERE n.nilai >= 70
ORDER BY m.nama;

-- cek mahasiswa yang belum punya transaksi (LEFT JOIN)
SELECT m.nim, m.nama
FROM mahasiswa m
LEFT JOIN transaksi t ON m.nim = t.nim
WHERE t.nim IS NULL;

tanpa foreign key dan index yang benar, JOIN di atas bisa lambat di data besar. dengan foreign key yang sudah ter-index, optimizer MySQL bisa pakai index dengan efisien.

5. ON DELETE dan ON UPDATE: Jangan Asal CASCADE

ini bagian yang paling sering bikin kecelakaan. aturannya dipasang saat definisi foreign key:

FOREIGN KEY (nim) REFERENCES mahasiswa(nim)
    ON DELETE CASCADE
    ON UPDATE CASCADE
AksiEfek saat data induk dihapus/diupdateKapan dipakai
CASCADEdata anak ikut terhapus/terupdate otomatisrelasi yang memang dependen, misal order_items ikut hapus saat orders dihapus
SET NULLkolom FK di anak jadi NULLrelasi opsional, misal mahasiswa.id_jurusan jadi NULL kalau jurusan dihapus
RESTRICTtolak penghapusan jika masih ada data anakpaling aman untuk data penting, misal tolak hapus mahasiswa jika masih ada transaksi
NO ACTIONsama seperti RESTRICT di MySQL (cek di akhir statement)sama dengan RESTRICT
SET DEFAULTdi MySQL di-parse tapi tidak didukung InnoDBjangan dipakai

pengalaman saya: jangan pakai CASCADE untuk relasi ke tabel master yang penting. saya pernah lihat satu DELETE FROM users WHERE id=5 menghapus ribuan baris di 6 tabel anak karena semua pakai CASCADE. untuk data transaksi, RESTRICT jauh lebih selamat, biarkan error muncul daripada data hilang diam-diam.

pola modern 2026: banyak tim sekarang pilih RESTRICT + soft delete (deleted_at) di aplikasi. jadi data tidak benar-benar dihapus, cuma ditandai, dan foreign key tetap konsisten. baca juga perbedaan DELETE dan TRUNCATE untuk paham kenapa TRUNCATE akan gagal jika masih ada foreign key.

Self-referencing foreign key

untuk struktur hirarki seperti kategori:

CREATE TABLE kategori (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    nama VARCHAR(100) NOT NULL,
    parent_id INT UNSIGNED NULL,
    FOREIGN KEY (parent_id) REFERENCES kategori(id) ON DELETE SET NULL
) ENGINE=InnoDB;

6. Update 2026: Pilih Primary Key yang Tepat

konsep PK dan FK tidak berubah, tapi cara kita memilih tipe primary key di 2026 sudah beda dibanding 5 tahun lalu.

MySQL 8.4 LTS jadi standar

sejak April 2026, MySQL 8.0 sudah End-of-Life. untuk project baru, pakai MySQL 8.4 LTS (didukung sampai 2032) atau MySQL 9.x Innovation. syntax foreign key tetap sama, tapi default authentication dan sql_mode lebih ketat. kalau tutorial lama kalian masih pakai MySQL 5.7, waktunya upgrade, karena 5.7 sudah tidak dapat security patch.

AUTO_INCREMENT vs UUIDv7 vs ULID

ini pertanyaan yang paling sering muncul di 2026:

Tipe PKKelebihanKekuranganCocok untuk
BIGINT UNSIGNED AUTO_INCREMENTpaling cepat, paling hemat storage (8 byte), urut, tidak fragmentasi indexbisa ditebak, tidak cocok untuk distributed DBmonolit, single database, 90% aplikasi UMKM/startup
UUIDv4global unik, tidak bisa ditebakacak, bikin clustered index terfragmentasi, lambat di data besarjangan dipakai untuk PK di 2026
UUIDv7global unik + time-ordered (urut waktu), tidak fragmentasi, standar baru IETF 202416 byte (2x BIGINT), string lebih panjangmicroservice, multi-region, sistem yang butuh ID tidak tebak (order_id, invoice)
ULID / Snowflake IDmirip UUIDv7, sortable, bisa generate di aplikasibutuh library tambahanalternatif UUIDv7, dipakai di Discord/Twitter pattern

rekomendasi praktis saya di 2026:

  • untuk users, posts, transactions di aplikasi biasa, tetap pakai BIGINT UNSIGNED AUTO_INCREMENT. simpel, cepat, dan semua ORM (Laravel, Prisma, Drizzle) optimasinya sudah matang.
  • untuk orders, payments, atau sistem yang butuh ID publik yang tidak bisa ditebak, pakai UUIDv7. di MySQL simpan sebagai BINARY(16) biar hemat, bukan VARCHAR(36):
    -- MySQL 8.0.13+ punya fungsi UUID_TO_BIN / BIN_TO_UUID
    CREATE TABLE orders (
        id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID())),
        user_id BIGINT UNSIGNED NOT NULL,
        total DECIMAL(12,2),
        FOREIGN KEY (user_id) REFERENCES users(id)
    ) ENGINE=InnoDB;
    
  • jangan pakai UUIDv4 acak sebagai PK kalau data sudah jutaan baris. insert akan lompat-lompat di B-Tree dan bikin performa drop.

InnoDB wajib, MyISAM tinggalkan

ini masih relevan di 2026 karena saya masih menemukan project lama pakai MyISAM. ingat: MyISAM tidak mendukung foreign key, CREATE TABLE ... FOREIGN KEY akan sukses tapi constraint diabaikan. selalu cek:

SHOW CREATE TABLE mahasiswa; -- lihat ENGINE=InnoDB
SHOW TABLE STATUS WHERE Name='mahasiswa';

kalau masih MyISAM, migrasi dulu. detailnya baca perbedaan MyISAM dan InnoDB.

Foreign key di era serverless & ORM

  • Prisma / Drizzle / Eloquent di 2026 sudah generate foreign key otomatis dari schema. di Prisma cukup user User @relation(fields: [userId], references: [id], onDelete: Cascade), migrasi akan bikin FK-nya. jangan dimatikan hanya karena “ribet”.
  • PlanetScale / Vitess: dulu terkenal tidak support foreign key, tapi sejak akhir 2023 Vitess sudah support FK secara native dan PlanetScale mengaktifkannya di 2024. jadi tidak ada alasan lagi skip FK karena pakai branching database.
  • Index otomatis: di MySQL 8.x, kalau kolom FK belum ada index, MySQL otomatis buatkan. tapi tetap definisikan index secara eksplisit biar nama index jelas dan explain plan lebih mudah dibaca.

7. Cara Cek, Validasi, dan Debug FK

-- lihat semua FK di database aktif
SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL;

-- cek relasi yang orphan (seharusnya 0 baris kalau FK benar)
SELECT t.nim FROM transaksi t
LEFT JOIN mahasiswa m ON t.nim = m.nim
WHERE t.nim IS NOT NULL AND m.nim IS NULL;

-- nonaktifkan sementara untuk import bulk (hati-hati, jangan lupa aktifkan lagi)
SET FOREIGN_KEY_CHECKS = 0;
-- ... import data ...
SET FOREIGN_KEY_CHECKS = 1;

-- tambah FK tanpa lock lama (MySQL 8.0+ online DDL)
ALTER TABLE transaksi
    ADD CONSTRAINT fk_transaksi_mahasiswa FOREIGN KEY (nim) REFERENCES mahasiswa(nim),
    ALGORITHM=INPLACE, LOCK=NONE;

FOREIGN_KEY_CHECKS=0 berguna saat restore dump, tapi jangan dipakai di aplikasi harian. kalau lupa diaktifkan lagi, data yatim bisa masuk tanpa warning.

8. Kesalahan Umum Pemula

  1. tipe data tidak sama persisINT di induk tapi BIGINT di anak, atau VARCHAR(10) vs VARCHAR(20), collation beda. MySQL akan error errno 150.
  2. pakai MyISAM — lupa ENGINE=InnoDB, FK diam-diam tidak aktif.
  3. lupa index di kolom FK — walau MySQL auto-create, di tabel besar alter bisa lambat.
  4. CASCADE sembrono — hapus satu user, semua transaksi, komentar, dan log ikut hilang.
  5. natural key yang sering berubah — pakai email sebagai PK lalu email diubah, semua FK harus CASCADE update, berat.
  6. tidak kasih nama constraint — biar DROP FOREIGN KEY mudah, selalu kasih nama fk_anak_induk.

9. Primary Key vs Unique Key vs Foreign Key

AspekPrimary KeyUnique KeyForeign Key
NULLtidak bolehboleh satu NULL (MySQL)boleh
Jumlah per tabel1banyakbanyak
Index otomatisya (clustered)ya (unique)auto dibuat jika belum ada
Fungsiidentitas bariscegah duplikatjaga relasi
Contohusers.idusers.emailorders.user_id

ingat: UNIQUE cocok untuk email, username, kode_voucher. PRIMARY KEY untuk id. FOREIGN KEY untuk user_id yang merujuk ke users(id).

10. Checklist Best Practice 2026

  • pakai BIGINT UNSIGNED AUTO_INCREMENT untuk PK internal, UUIDv7 BINARY(16) untuk ID publik/terdistribusi
  • selalu ENGINE=InnoDB, cek SHOW CREATE TABLE
  • kasih nama constraint yang jelas: fk_transaksi_mahasiswa, fk_nilai_mk
  • pakai RESTRICT sebagai default, CASCADE hanya jika paham dampaknya
  • untuk data penting, pertimbangkan soft delete (deleted_at) daripada hard delete
  • samakan tipe, panjang, charset, dan collation antara FK dan PK yang dirujuk
  • buat index eksplisit untuk FK, jangan andalkan auto-create saja
  • test migrasi di staging dengan data jutaan baris sebelum ke production, terutama ALTER TABLE ... ADD FOREIGN KEY

Kesimpulan

  • primary key adalah identitas unik tiap baris, satu per tabel, NOT NULL, dan jadi clustered index. pilih BIGINT AUTO_INCREMENT untuk monolit, UUIDv7 untuk sistem terdistribusi.
  • foreign key adalah penghubung antar tabel yang menjaga integritas data. boleh duplikat, boleh NULL, dan boleh banyak per tabel.
  • kombinasi keduanya memungkinkan JOIN yang cepat dan mencegah data yatim. tanpa FK, aplikasi harus jaga relasi manual dan itu sumber bug paling sering.
  • di MySQL 2026, selalu pakai InnoDB, pahami ON DELETE dengan hati-hati, dan manfaatkan fitur modern seperti UUID_TO_BIN dan online DDL. untuk pondasi yang lebih dalam, lanjutkan ke perbedaan MyISAM dan InnoDB, perbedaan DELETE dan TRUNCATE, dan perbedaan trigger dan stored procedure biar pemahaman database kalian bulat.

Baca Juga Mengenai :

Pertanyaan yang Sering Diajukan

Apa itu primary key?

Primary key adalah kolom atau kombinasi kolom yang secara unik mengidentifikasi setiap baris di tabel. nilainya harus unik dan tidak boleh NULL, hanya boleh satu per tabel, dan otomatis jadi clustered index di InnoDB.

Apa fungsi foreign key?

Foreign key menghubungkan tabel anak ke tabel induk dengan merujuk primary key atau unique key di tabel lain. fungsinya menjaga referential integrity agar tidak ada data yatim, misal transaksi yang merujuk mahasiswa yang sudah tidak ada.

Apakah satu tabel bisa punya lebih dari satu primary key?

Tidak. satu tabel hanya boleh punya satu primary key, tapi primary key itu boleh composite (gabungan beberapa kolom). jika butuh keunikan lain, pakai UNIQUE KEY.

Apakah foreign key wajib dibuat?

Secara teknis tidak wajib, tapi sangat disarankan. tanpa foreign key, validasi relasi harus dilakukan manual di aplikasi dan rawan bug. di 2026 hampir semua project production pakai foreign key dengan engine InnoDB.

Apa beda primary key dengan unique key?

Primary key tidak boleh NULL dan hanya satu per tabel, otomatis clustered index. unique key boleh satu NULL (di MySQL), boleh banyak per tabel, dan fungsinya mencegah duplikat bukan sebagai identitas utama.

Untuk primary key di 2026 lebih baik AUTO_INCREMENT atau UUIDv7?

Untuk aplikasi monolit single database, BIGINT UNSIGNED AUTO_INCREMENT masih paling simpel dan cepat. untuk sistem terdistribusi, microservice, atau butuh ID yang tidak bisa ditebak, UUIDv7 (time-ordered) jadi standar baru 2026 karena tidak bikin index fragmentation seperti UUIDv4.

Kapan pakai ON DELETE CASCADE dan kapan RESTRICT?

Pakai CASCADE hanya untuk data anak yang memang harus ikut hilang, misal item_order ikut terhapus saat order dihapus. untuk data penting, pakai RESTRICT atau NO ACTION biar MySQL menolak penghapusan dan mencegah kehilangan data masal tanpa sengaja.

Kenapa foreign key saya error saat CREATE TABLE?

Penyebab paling umum: engine masih MyISAM (tidak support FK), tipe data kolom FK dan PK tidak sama persis (INT vs BIGINT), atau collation/charset beda. pastikan dua-duanya InnoDB, tipe dan panjang sama, dan tabel induk sudah ada sebelum tabel anak.

Sigit Nurhanafi avatar
Full-stack developer & technical writer. Berpengalaman di PHP, Laravel, NodeJS, MySQL, dan Python. Aktif menulis tutorial pemrograman dan maintaining open-source projects di PemburuKode sejak 2021.