Primary key bertipe integer yang naik terus (1, 2, 3, …) cukup untuk satu database, tetapi skenarionya berubah begitu baris dibuat di banyak tempat sekaligus: aplikasi mobile yang bekerja offline, microservice yang masing-masing punya database, atau data cabang yang harus digabung ke pusat. Dua counter yang berjalan sendiri pasti menghasilkan nomor yang sama dan bertabrakan saat data disatukan. Tipe uuid (Universally Unique Identifier) menjawab masalah itu: identifier 128 bit yang bisa dibuat oleh pihak mana pun, kapan pun, tanpa koordinasi, dengan peluang tabrakan yang secara praktis nol. Chapter ini membahas bentuk penulisan UUID, pemakaiannya sebagai primary key, dan cara menghasilkannya lewat gen_random_uuid().
Bentuk dan Penulisan UUID #
Sebuah UUID terdiri dari 32 digit heksadesimal — 128 bit data — yang ditulis dalam lima kelompok terpisah garis: 8-4-4-4-12. PostgreSQL menyimpan nilainya sebagai 16 byte biner (bukan teks 36 karakter), sehingga perbandingan dua UUID jauh lebih murah daripada membandingkan dua string. Parser inputnya juga longgar: huruf besar diterima dan hasilnya dikembalikan huruf kecil, sedangkan garis pemisah boleh dibuang seluruhnya:
-- bentuk standar: 32 digit heksadesimal dalam 5 kelompok
SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid AS bentuk_standar;
-- huruf besar diterima, hasil tetap dikembalikan huruf kecil
SELECT 'A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'::uuid AS huruf_besar;
-- garis pemisah boleh dibuang seluruhnya
SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a11'::uuid AS tanpa_garis;
-- dua penulisan yang berbeda dibaca sebagai nilai yang sama
SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid
= 'A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'::uuid AS nilai_sama;Output yang Dihasilkan:
bentuk_standar
--------------------------------------
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
(1 row)
huruf_besar
--------------------------------------
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
(1 row)
tanpa_garis
--------------------------------------
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
(1 row)
nilai_sama
------------
t
(1 row)Ketiga literal berbeda bentuk di atas menghasilkan nilai UUID yang persis sama, dan perbandingan kesamaannya mengembalikan t. Kebebasan bentuk input ini berguna di dunia nyata: aplikasi lain mungkin mengirim UUID dengan huruf besar atau tanpa garis pemisah, dan PostgreSQL tetap menerimanya sebagai nilai identik — kanonisasi ke huruf kecil dilakukan oleh server, bukan aplikasi.
UUID sebagai Primary Key #
Pemakaian paling umum tipe ini adalah kolom identitas. Deklarasinya sama seperti tipe lain: uuid PRIMARY KEY. Contoh berikut menyimpan data pengiriman paket dengan tiga UUID yang kita tulis secara tetap:
CREATE TABLE pengiriman (
id uuid PRIMARY KEY,
nama_penerima text NOT NULL,
kota text NOT NULL
);
INSERT INTO pengiriman (id, nama_penerima, kota) VALUES
('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Budi', 'Jakarta'),
('3f2504e0-4f89-11d3-9a0c-0305e82c3301', 'Siti', 'Bandung'),
('00000000-0000-0000-0000-000000000001', 'Ani', 'Surabaya');
-- urutan hasil ORDER BY mengikuti nilai byte uuid,
-- bukan urutan penyisipan baris
SELECT id, nama_penerima, kota
FROM pengiriman
ORDER BY id;
-- pencarian satu baris dengan nilai uuid tetap
SELECT nama_penerima, kota
FROM pengiriman
WHERE id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';Output yang Dihasilkan:
CREATE TABLE
INSERT 0 3
id | nama_penerima | kota
--------------------------------------+---------------+----------
00000000-0000-0000-0000-000000000001 | Ani | Surabaya
3f2504e0-4f89-11d3-9a0c-0305e82c3301 | Siti | Bandung
a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 | Budi | Jakarta
(3 rows)
nama_penerima | kota
---------------+---------
Budi | Jakarta
(1 row)Perhatikan hasil ORDER BY id: baris Ani muncul pertama meskipun disisipkan terakhir. Pengurutan UUID membandingkan nilai byte demi byte, bukan urutan pembuatan — berbeda total dari integer yang naik. Konsekuensinya, tabel ber-UUID tidak menyimpan informasi “baris mana lebih dulu ada”, dan itulah salah satu daya tariknya: identifier tidak membocorkan urutan maupun jumlah baris. Query kedua memperlihatkan pencarian satu baris dengan WHERE id = '...' yang berjalan lewat index primary key.
gen_random_uuid sebagai Nilai Default #
Menulis UUID manual hanya masuk akal untuk data contoh. Di aplikasi nyata, server yang menghasilkannya lewat fungsi gen_random_uuid(), dan pola yang paling rapi adalah menjadikannya DEFAULT kolom:
CREATE TABLE aktivitas (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
pengguna text NOT NULL,
aksi text NOT NULL
);
-- id tidak disebut; server mengisinya lewat gen_random_uuid()
INSERT INTO aktivitas (pengguna, aksi) VALUES
('Budi', 'login'),
('Siti', 'unggah foto');
-- nilai acak tidak bisa diramal sebelumnya,
-- jadi tampilkan keberadaannya saja, bukan nilainya
SELECT pengguna, aksi, id IS NOT NULL AS id_terisi
FROM aktivitas
ORDER BY pengguna, aksi;Output yang Dihasilkan:
CREATE TABLE
INSERT 0 2
pengguna | aksi | id_terisi
----------+-------------+-----------
Budi | login | t
Siti | unggah foto | t
(2 rows)Kedua baris masuk tanpa menyebut id, dan kolom id_terisi bernilai t untuk keduanya — bukti gen_random_uuid() mengisi kolom tersebut secara otomatis. Query ini sengaja tidak menampilkan nilai UUID-nya: hasil gen_random_uuid() berbeda pada setiap eksekusi (itulah sifat nilai acak), sehingga nilainya tidak bisa dijadikan output contoh yang tetap. Fungsi ini menghasilkan UUID versi 4 — 122 bit acak murni — dan merupakan bagian inti PostgreSQL sejak versi 13.
Primary Key Menolak UUID Duplikat #
Menyalin baris dari sistem lain kadang membawa identifier yang sudah dipakai. Primary key menolaknya mentah-mentah, seperti demo berikut yang mencoba memasukkan UUID milik Budi dua kali:
INSERT INTO pengiriman (id, nama_penerima, kota) VALUES
('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Tono', 'Semarang');Output yang Dihasilkan:
psql:uuid-duplikat.sql:2: ERROR: duplicate key value violates unique constraint "pengiriman_pkey"
DETAIL: Key (id)=(a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11) already exists.Error duplicate key value violates unique constraint lengkap dengan nilai kunci yang bentrok (Key (id)=(a0eebc99-…)) — informasi yang cukup untuk melacak baris asal di sistem sumbernya. Penanganan bentrok semacam ini (misalnya lewat ON CONFLICT) dibahas di chapter ON CONFLICT pada Section Manipulasi Data.
Input UUID yang Tidak Valid #
Teks yang bukan UUID ditolak sejak proses parsing, jauh sebelum constraint sempat diperiksa:
INSERT INTO pengiriman (id, nama_penerima, kota) VALUES
('bukan-uuid', 'Tono', 'Semarang');Output yang Dihasilkan:
psql:invalid-uuid.sql:2: ERROR: invalid input syntax for type uuid: "bukan-uuid"
LINE 2: ('bukan-uuid', 'Tono', 'Semarang');
^Error invalid input syntax for type uuid menandakan teks tersebut tidak terdiri dari 32 digit heksadesimal yang valid. Validasi ini mencegah sampah masuk ke kolom identitas — tugas yang pada kolom text harus kita bangun sendiri lewat CHECK atau validasi aplikasi.
Kapan UUID Dipakai #
| Situasi | Alasan memilih UUID |
|---|---|
| Microservice terdistribusi | Tiap layanan membuat ID sendiri tanpa koordinasi |
| Penggabungan data cabang | Baris dari database berbeda tidak bertabrakan saat disatukan |
| Aplikasi mobile offline | ID dibuat di perangkat sejak sebelum tersinkron ke server |
| Identifier di URL publik | Nilai acak tidak membocorkan jumlah baris seperti counter naik |
Keputusan ini tidak gratis: UUID memakan 16 byte — empat kali integer — sehingga index-nya lebih besar, dan nilai acak versi 4 tersebar di seluruh B-tree sehingga penulisan berurutan tidak terlokalisasi. Untuk tabel kecil dan sedang perbedaan ini nyaris tak terasa; untuk tabel sangat besar, trade-off-nya perlu ditimbang. Panduan lengkap memilih antara integer, bigint, dan uuid dibahas di chapter Memilih Tipe Data di akhir section ini.
Identifier baris kini bisa dibuat tanpa pusat penerbit nomor. Topik berikutnya pindah ke kendali nilai yang justru sebaliknya — daftar tetap yang tertutup: tipe ENUM untuk membatasi kolom pada himpunan nilai resmi.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
default-gen-random.sql
Unduh berkasCREATE TABLE aktivitas (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
pengguna text NOT NULL,
aksi text NOT NULL
);
-- id tidak disebut; server mengisinya lewat gen_random_uuid()
INSERT INTO aktivitas (pengguna, aksi) VALUES
('Budi', 'login'),
('Siti', 'unggah foto');
-- nilai acak tidak bisa diramal sebelumnya,
-- jadi tampilkan keberadaannya saja, bukan nilainya
SELECT pengguna, aksi, id IS NOT NULL AS id_terisi
FROM aktivitas
ORDER BY pengguna, aksi;
invalid-uuid.sql
Unduh berkasINSERT INTO pengiriman (id, nama_penerima, kota) VALUES
('bukan-uuid', 'Tono', 'Semarang');
literal-uuid.sql
Unduh berkas-- bentuk standar: 32 digit heksadesimal dalam 5 kelompok
SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid AS bentuk_standar;
-- huruf besar diterima, hasil tetap dikembalikan huruf kecil
SELECT 'A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'::uuid AS huruf_besar;
-- garis pemisah boleh dibuang seluruhnya
SELECT 'a0eebc999c0b4ef8bb6d6bb9bd380a11'::uuid AS tanpa_garis;
-- dua penulisan yang berbeda dibaca sebagai nilai yang sama
SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid
= 'A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'::uuid AS nilai_sama;
tabel-pk.sql
Unduh berkasCREATE TABLE pengiriman (
id uuid PRIMARY KEY,
nama_penerima text NOT NULL,
kota text NOT NULL
);
INSERT INTO pengiriman (id, nama_penerima, kota) VALUES
('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Budi', 'Jakarta'),
('3f2504e0-4f89-11d3-9a0c-0305e82c3301', 'Siti', 'Bandung'),
('00000000-0000-0000-0000-000000000001', 'Ani', 'Surabaya');
-- urutan hasil ORDER BY mengikuti nilai byte uuid,
-- bukan urutan penyisipan baris
SELECT id, nama_penerima, kota
FROM pengiriman
ORDER BY id;
-- pencarian satu baris dengan nilai uuid tetap
SELECT nama_penerima, kota
FROM pengiriman
WHERE id = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11';
uuid-duplikat.sql
Unduh berkasINSERT INTO pengiriman (id, nama_penerima, kota) VALUES
('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'Tono', 'Semarang');