Pilih auto-increment jika aplikasi Anda memakai satu database, ID tidak diekspos ke publik, dan Anda ingin index sekecil serta insert secepat mungkin. Pilih UUID (sebaiknya versi 7) atau ULID jika ID perlu dibuat di luar database, digabung dari beberapa sumber, atau tidak boleh mudah ditebak di URL. Hindari UUID v4 acak sebagai primary key di tabel MySQL besar, karena urutan acaknya memperlambat insert dan membengkakkan index.
Artikel ini membandingkan keduanya dari sisi keamanan (enumerasi), penggabungan data, ukuran index, dan performa insert. Setelah itu kita bahas perbedaan UUID v4 dan v7, apa itu ULID, serta cara memakainya di Laravel dengan trait HasUuids dan HasUlids. Untuk melihat bentuk tiap jenis ID, Anda bisa membuat contoh di UUID Generator.
Apa itu auto-increment dan UUID?
Auto-increment adalah angka berurutan yang dibuat database: 1, 2, 3, dan seterusnya. Di MySQL biasanya bertipe BIGINT UNSIGNED (8 byte). Di Laravel, ini yang dibuat oleh $table->id().
UUID (Universally Unique Identifier) adalah angka 128 bit (16 byte) yang biasanya ditulis sebagai 36 karakter heksadesimal dengan tanda hubung, misalnya 0192f3a4-7c1e-7b2a-9f3d-5e8a1c2b4d6f. UUID dirancang agar bisa dibuat di mana saja, di server mana pun, di browser, bahkan offline, tanpa koordinasi, dengan peluang bentrok yang praktis nol. Standarnya kini diatur di RFC 9562, yang terbit tahun 2024 menggantikan RFC 4122.
Perbandingan: auto-increment vs UUID
| Aspek | Auto-increment | UUID v4 | UUID v7 / ULID |
|---|---|---|---|
| Ukuran | 8 byte (BIGINT) | 16 byte biner, 36 karakter teks | 16 byte biner; teks 36 (UUID) atau 26 (ULID) karakter |
| Urutan | Berurutan | Acak | Berurutan menurut waktu |
| Bisa ditebak | Ya | Tidak | Tidak, tetapi waktu pembuatan terbaca |
| Dibuat di luar DB | Tidak | Ya | Ya |
| Performa insert MySQL | Sangat baik | Buruk di tabel besar | Baik |
| Mudah dibaca manusia | Sangat | Sulit | Sulit |
1. Enumerasi: ID yang bisa ditebak
URL seperti /invoice/1052 mengundang orang mencoba /invoice/1051 dan /invoice/1053. Jika aplikasi lupa mengecek hak akses, data orang lain bocor. Kerentanan ini dikenal sebagai IDOR (Insecure Direct Object Reference). Selain itu, ID berurutan membocorkan informasi bisnis: pesaing bisa memperkirakan jumlah pesanan Anda per bulan hanya dengan membuat dua pesanan berselang sebulan.
UUID membuat ID praktis mustahil ditebak. Namun ini bukan pengganti otorisasi. UUID bisa bocor lewat log, email, atau riwayat browser. Policy atau Gate di Laravel tetap wajib dipasang. Anggap UUID sebagai lapisan tambahan, bukan satu-satunya pertahanan.
2. Penggabungan data dan sistem terdistribusi
Auto-increment hanya unik di dalam satu tabel pada satu database. Jika Anda menggabungkan data dari dua cabang toko, memindahkan data antar-environment, atau melakukan sharding, ID 1052 di database A bisa bentrok dengan ID 1052 di database B. UUID unik secara global, sehingga penggabungan aman tanpa perlu memetakan ulang ID dan foreign key.
Keuntungan lain: aplikasi bisa membuat ID sebelum menyimpan ke database. Ini berguna untuk aplikasi mobile yang offline, antrian (queue) yang perlu ID lebih dulu untuk idempotensi, atau saat Anda membuat parent dan child dalam satu batch tanpa menunggu lastInsertId.
3. Ukuran index
Di InnoDB (engine bawaan MySQL), tabel disimpan secara fisik terurut menurut primary key (clustered index), dan setiap secondary index juga menyimpan salinan primary key. Akibatnya, ukuran primary key ikut memengaruhi semua index lain. Mengganti BIGINT 8 byte dengan BINARY(16) menggandakan ukurannya; mengganti dengan CHAR(36) membuatnya lebih besar lagi. Pada tabel dengan banyak index dan jutaan baris, selisih ini berarti lebih banyak RAM yang dibutuhkan agar index muat di buffer pool.
Foreign key juga ikut membesar. Setiap tabel yang mereferensikan tabel ber-UUID harus menyimpan kolom 16 atau 36 byte, bukan 8 byte.
4. Performa insert
Karena InnoDB menyusun data menurut primary key, ID berurutan selalu ditambahkan di ujung index. Halaman (page) terakhir terisi penuh, lalu halaman baru dibuat. Ini sangat efisien.
UUID v4 sepenuhnya acak, jadi setiap baris baru harus disisipkan di posisi acak di tengah index. Database harus memuat halaman yang berbeda-beda ke memori dan sering memecah halaman (page split), sehingga halaman menjadi setengah kosong dan index terfragmentasi. Selama tabel masih kecil dan muat di memori, efeknya tidak terasa. Pada tabel besar yang melebihi buffer pool, insert bisa melambat secara nyata. Inilah alasan utama munculnya UUID berurutan waktu.
UUID v4 vs UUID v7
UUID v4 berisi 122 bit acak (6 bit sisanya menandai versi dan varian). Sederhana, didukung di mana-mana (crypto.randomUUID() di JavaScript, Str::uuid() di Laravel), dan tidak membocorkan informasi apa pun. Kelemahannya adalah keacakan tadi saat dipakai sebagai primary key.
UUID v7, yang distandarkan di RFC 9562, mengisi 48 bit pertama dengan timestamp Unix dalam milidetik, lalu sisanya dengan bit acak. Karena awalnya adalah waktu, UUID v7 yang dibuat belakangan selalu lebih besar daripada yang dibuat sebelumnya (dalam presisi milidetik). Hasilnya, insert kembali menumpuk di ujung index seperti auto-increment, tetapi Anda tetap mendapat keunikan global dan ID yang tidak bisa ditebak.
UUID v4: 9b2e4c1a-3f7d-4e8b-a1c6-2d5f8e0b7a93 ← acak total, digit ke-13 = 4
UUID v7: 0192f3a4-7c1e-7b2a-9f3d-5e8a1c2b4d6f ← 12 digit awal = waktu, digit ke-13 = 7
Satu hal yang perlu disadari: UUID v7 membocorkan kapan record dibuat, karena timestamp-nya bisa dibaca siapa saja. Untuk sebagian besar aplikasi ini tidak masalah, tetapi pertimbangkan jika waktu pembuatan akun atau dokumen adalah informasi sensitif.
Apa itu ULID?
ULID (Universally Unique Lexicographically Sortable Identifier) lahir sebelum UUID v7 dengan ide yang sama: 48 bit timestamp milidetik diikuti 80 bit acak, total 128 bit. Perbedaannya ada pada cara penulisan: ULID memakai Crockford Base32 sehingga hanya 26 karakter, tanpa tanda hubung, dan tanpa huruf yang mudah tertukar seperti I, L, O, dan U. Contohnya 01J9ZQ3K7M8N2P4R6S8T0V2W4X.
Karena sama-sama 128 bit dan berurutan waktu, ULID dan UUID v7 punya sifat performa yang mirip. Pilih UUID v7 jika Anda ingin format standar yang dikenali database dan library (misalnya kolom uuid native di PostgreSQL). Pilih ULID jika Anda lebih suka string yang lebih pendek dan nyaman di URL.
Menyimpan UUID di MySQL dengan benar
Ada dua pilihan penyimpanan:
Berlangganan gratis. Artikel & tutorial coding terbaru langsung ke email kamu. Tanpa spam.
CHAR(36): mudah dibaca dan di-debug. Ini yang dibuat oleh$table->uuid()di Laravel untuk MySQL. Ukurannya lebih dari dua kali lipat BINARY(16).BINARY(16): paling hemat. MySQL 8 menyediakanUUID_TO_BIN()danBIN_TO_UUID()untuk konversi. Kekurangannya, Anda perlu konversi setiap kali membaca dan menulis, sehingga kode aplikasi sedikit lebih rumit.
CREATE TABLE pesanan (
id BINARY(16) PRIMARY KEY,
total DECIMAL(12,2) NOT NULL
);
INSERT INTO pesanan (id, total)
VALUES (UUID_TO_BIN('0192f3a4-7c1e-7b2a-9f3d-5e8a1c2b4d6f'), 150000);
SELECT BIN_TO_UUID(id) AS id, total FROM pesanan;
Perlu diketahui, fungsi UUID() bawaan MySQL menghasilkan UUID versi 1, bukan v4 atau v7. Argumen kedua UUID_TO_BIN(uuid, 1) menukar bagian waktu v1 agar berurutan; untuk UUID v7, jangan gunakan argumen itu karena v7 sudah berurutan secara alami. Untuk tabel kecil sampai menengah, CHAR(36) bawaan Laravel biasanya cukup. Pertimbangkan BINARY(16) saat tabel sudah sangat besar. Jika Anda masih baru dengan konsep index, baca dulu panduan belajar MySQL.
UUID dan ULID di Laravel: HasUuids dan HasUlids
Laravel menyediakan trait yang membuat ID secara otomatis saat model dibuat. Migrasinya:
<?php
Schema::create('pesanan', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->foreignUuid('pelanggan_id')->constrained('pelanggan');
$table->decimal('total', 12, 2);
$table->timestamps();
});
// Versi ULID
Schema::create('tiket', function (Blueprint $table) {
$table->ulid('id')->primary();
$table->foreignUlid('pesanan_id')->constrained('pesanan');
$table->timestamps();
});
Modelnya:
<?php
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
class Pesanan extends Model
{
use HasUuids;
protected $table = 'pesanan';
}
$pesanan = Pesanan::create(['pelanggan_id' => $id, 'total' => 150000]);
echo $pesanan->id; // UUID otomatis
Untuk ULID, cukup ganti dengan use HasUlids;. Trait ini otomatis mengatur $incrementing = false dan tipe key menjadi string, jadi Anda tidak perlu mengaturnya manual.
Perhatikan perbedaan versi Laravel. Di Laravel 10 dan 11, HasUuids menghasilkan "ordered UUID" (Str::orderedUuid()), yaitu UUID yang diawali timestamp agar tetap berurutan. Laravel 11 juga menyediakan trait HasVersion7Uuids dan helper Str::uuid7(). Mulai Laravel 12, HasUuids secara default menghasilkan UUID v7, dan trait HasVersion4Uuids tersedia bagi yang ingin perilaku lama. Cek versi Laravel Anda sebelum mengandalkan format tertentu.
Pendekatan hibrida: ID internal + ID publik
Banyak tim memilih jalan tengah: primary key tetap BIGINT auto-increment untuk performa join dan index, lalu ditambah kolom uuid atau ulid unik yang dipakai di URL dan API. Di Laravel, Anda bisa memakai trait yang sama dengan menentukan kolomnya:
<?php
class Pesanan extends Model
{
use HasUuids;
public function uniqueIds(): array
{
return ['uuid']; // kolom 'id' tetap auto-increment
}
public function getRouteKeyName(): string
{
return 'uuid'; // route model binding memakai uuid
}
}
Dengan cara ini, URL menjadi /pesanan/0192f3a4-..., sementara relasi internal tetap memakai integer kecil yang cepat. Desain ini juga cocok untuk REST API publik.
Kesalahan umum
- Mengandalkan UUID sebagai keamanan. Tetap pasang otorisasi di setiap endpoint.
- Memakai UUID v4 sebagai primary key tabel besar di MySQL tanpa menyadari dampaknya pada insert dan index.
- Lupa mengubah tipe foreign key. Memakai
foreignId()untuk mereferensikan tabel ber-UUID akan gagal; gunakanforeignUuid()atauforeignUlid(). - Mengurutkan berdasarkan UUID v4 dan berharap hasilnya sesuai waktu pembuatan. Gunakan kolom
created_at, atau pakai v7/ULID. - Mencampur huruf besar dan kecil saat membandingkan UUID dalam bentuk string. Normalisasi ke huruf kecil.
Ringkasan: mana yang dipilih?
- Aplikasi kecil–menengah, satu database, ID tidak terlihat publik: auto-increment.
- ID tampil di URL/API publik: UUID v7 atau ULID, atau hibrida BIGINT + kolom publik.
- Data digabung dari banyak sumber atau dibuat offline: UUID v7 atau ULID.
- Butuh ID acak tanpa informasi waktu: UUID v4, sebaiknya bukan sebagai primary key tabel yang sangat besar.
FAQ
Apakah UUID bisa bentrok?
Secara teori bisa, tetapi peluangnya sangat kecil sehingga dalam praktik diabaikan, selama generator memakai sumber acak yang baik. Tetap pasang constraint PRIMARY KEY atau UNIQUE agar database menolak duplikat jika itu terjadi.
Apakah UUID memperlambat database?
UUID v4 acak bisa memperlambat insert dan memperbesar index pada tabel besar di MySQL InnoDB. UUID v7 dan ULID jauh lebih ramah karena berurutan menurut waktu, meskipun ukurannya tetap dua kali BIGINT.
Apa beda UUID v7 dan ULID?
Isinya mirip: timestamp milidetik 48 bit plus bit acak. UUID v7 mengikuti format UUID standar (36 karakter) dan dikenali tipe uuid di database, sedangkan ULID ditulis dalam Base32 sepanjang 26 karakter.
Bagaimana membuat UUID di JavaScript?
Gunakan crypto.randomUUID(), yang tersedia di browser modern (dalam konteks aman/HTTPS) dan Node.js. Fungsi ini menghasilkan UUID v4.
Bolehkah mengganti primary key auto-increment menjadi UUID di tabel yang sudah ada?
Bisa, tetapi butuh migrasi bertahap: tambah kolom UUID, isi untuk semua baris, perbarui semua foreign key, lalu pindahkan primary key. Untuk tabel yang sudah berjalan di produksi, pendekatan hibrida (menambah kolom UUID publik saja) biasanya jauh lebih aman.