← blog · 1 Kasım 2026

Birincil Anahtar Seçimi: bigint, UUIDv4 ve UUIDv7 Arasında Karar Vermek

Otomatik artan tamsayı iş hacminizi sızdırır, rastgele UUID büyük tablolarda indeks yazımını dağıtır ve WAL'ı şişirir. Anahtarın iki görevini ayırmak, UUIDv7'yi neden varsayılan yapmak gerektiği ve metin olarak saklama, int ile başlama, boşluksuz dizi gibi tuzaklar.

Yeni bir tablo açarken birincil anahtar genelde üzerinde en az düşünülen karardır: ya alışkanlıkla otomatik artan bir tamsayı yazılır ya da "dağıtık sistemlerde UUID kullanılır" diye rastgele bir UUID. İkisi de yıllar sonra pahalıya patlayabilir. Tamsayı dışarı sızınca kaç müşteriniz olduğunu söyler, 32 bitlik sütun dolunca tabloyu yeniden yazmanız gerekir. Rastgele UUID ise büyük tablolarda indeks yazımını dağıtır, önbelleği boşa harcar ve WAL hacmini şişirir. Bu yazıda üç yaygın seçeneği, hangisinin nerede doğru olduğunu ve en sık düşülen tuzakları anlatıyoruz.

Anahtarın aslında iki görevi var

Birincil anahtar iki ayrı iş yapar ve sorunların çoğu bu ikisinin karıştırılmasından çıkar:

  1. Depolama kimliği: veritabanının satırı bulmak, yabancı anahtarlarla bağlamak ve indekslemek için kullandığı değer. Burada küçük, sıralı ve ucuz karşılaştırılan bir değer istersiniz.
  2. Dışarıya gösterilen kimlik: URL'de, API cevabında, destek talebinde görünen değer. Burada tahmin edilemeyen, iş hacmini ele vermeyen ve sistemler arasında taşınabilen bir değer istersiniz.

Tek sütun ikisini birden yapabilir, ama ne zaman yapamayacağını bilmek gerekir.

Seçenek 1: bigint ve kimlik sütunu

PostgreSQL'de bugün önerilen yazım serial değil, standart SQL kimlik sütunudur:

CREATE TABLE siparis (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    olusturma timestamptz NOT NULL DEFAULT now()
);

GENERATED ALWAYS uygulamanın elle değer yazmasını engeller; elle değer yazılıp dizinin geride kaldığı ve bir sonraki eklemenin benzersizlik hatası verdiği klasik durumu baştan kapatır. Sekiz baytlık değer indekste az yer tutar, her yeni satır indeksin sağ ucuna eklenir, sayfalar sıcak kalır.

Zayıf yanları dışarıyla ilgilidir. Ardışık değer, URL'de görünürse sipariş hacminizi ve büyüme hızınızı herkese söyler. İki farklı veritabanında üretilen kayıtları birleştirmek ya da kaydı veritabanına yazmadan önce istemcide kimlik üretmek mümkün değildir.

Seçenek 2: UUIDv4

Tamamen rastgele 122 bit taşır, çakışma pratikte yoktur, her yerde üretilebilir ve hiçbir şey sızdırmaz. Dışarıya gösterilen kimlik olarak çok iyidir.

Birincil anahtar olarak ise büyük tablolarda maliyeti vardır. B-tree indeksinde her yeni değer rastgele bir yaprak sayfaya düşer. Tablo belleğe sığdığı sürece bu fark edilmez. İndeks paylaşılan belleği aştığında her ekleme büyük olasılıkla soğuk bir sayfayı diskten okumayı gerektirir, sayfa bölünmeleri artar ve indeks doluluk oranı düşer. PostgreSQL'de bir kontrol noktasından sonra ilk kez değişen her sayfa WAL'a tam olarak yazıldığı için, rastgele dağılmış eklemeler aynı veri miktarı için çok daha fazla WAL üretir. Bu da replikasyon gecikmesi ve yedek boyutu olarak geri döner.

Birincil anahtarın kümelenmiş indeks olduğu motorlarda (MySQL InnoDB gibi) etki daha da serttir: satırların kendisi anahtar sırasıyla tutulur ve her ikincil indeks birincil anahtarı da taşır, yani 16 baytlık rastgele anahtar tüm ikincil indeksleri büyütür.

Seçenek 3: UUIDv7

RFC 9562 ile tanımlanan UUIDv7, ilk 48 bitine milisaniye çözünürlüklü Unix zaman damgası koyar, kalanını rastgele bitlerle doldurur. Sonuç hâlâ 128 bitlik, her yerde üretilebilen bir UUID'dir, ama zamanla artan bir değerdir: yeni satırlar indeksin sağ ucuna yığılır ve bigint'e yakın bir yazma davranışı elde edilir.

PostgreSQL 18 ile uuidv7() fonksiyonu yerleşik geldi:

CREATE TABLE belge (
    id uuid PRIMARY KEY DEFAULT uuidv7(),
    baslik text NOT NULL
);

Daha eski sürümlerde değeri uygulama tarafında üretmeniz gerekir; çoğu dilin bakımlı UUID kütüphaneleri artık v7 üretebiliyor. Her durumda sütun tipi yerel uuid olmalıdır.

Hangisini seçmeli

Taraf tutalım: yeni bir projede varsayılan olarak UUIDv7 kullanın. Dağıtık üretim, sistemler arası birleştirme ve istemci tarafında kimlik verme gibi ihtiyaçlar sonradan ortaya çıktığında anahtarı değiştirmek çok pahalıdır. UUIDv7 bu esnekliği verirken rastgele UUID'nin yazma maliyetinin çoğunu ortadan kaldırır.

bigint şu durumlarda daha doğrudur: kimliği hiçbir zaman dışarıya göstermeyeceğiniz iç tablolar, milyarlarca satırlı ve çok sayıda yabancı anahtarla bağlanan olay tabloları (her bağlantıda sekiz bayt fark birikir), ve tek veritabanından asla çıkmayacağı belli olan sistemler.

UUIDv4'ü birincil anahtar olarak seçmenin bugün tek iyi gerekçesi, oluşturma zamanının kimlikten okunmasının kabul edilemez olmasıdır. O durumda bile çoğu zaman daha iyi yol, depolamada sıralı bir anahtar tutup ayrı bir sütunda v4 göstermektir.

Tuzaklar

UUID'yi metin olarak saklamak. varchar(36) ya da char(36) sütunu yerel tipin iki katından fazla yer tutar, karşılaştırması harf duyarlılığı ve karşılaştırma kurallarıyla yavaşlar, büyük harfli ve küçük harfli aynı değer iki farklı satır gibi davranabilir. Yerel tip yoksa 16 baytlık ikili sütun kullanın.

int ile başlamak. 32 bitlik işaretli tamsayı yaklaşık 2,1 milyarda biter. Silinen satırlar ve geri alınan işlemler de değer harcadığı için bu sınıra beklenenden önce gelinir. Sütunu sonradan bigint'e çevirmek PostgreSQL'de tabloyu yeniden yazar ve onu gösteren tüm yabancı anahtar sütunlarını da değiştirmeyi gerektirir. Başta bigint seçmenin maliyeti neredeyse sıfırdır.

Dizinin boşluksuz olduğunu varsaymak. Dizi değerleri işlem geri alınsa da geri dönmez, sunucu yeniden başladığında önbelleğe alınmış değerler atlanabilir. Fatura numarası gibi yasal olarak boşluksuz olması gereken bir numarayı birincil anahtardan türetmeyin; onu ayrı bir sayaçla, gerekirse kilitleyerek üretin.

UUIDv7'nin kesin sıra verdiğini sanmak. Zaman damgası milisaniye çözünürlüğündedir ve farklı makinelerin saatleri birbirinden kayabilir. Aynı milisaniyede farklı sunucularda üretilen iki kimliğin sırası rastgeledir. "Son eklenen kayıt" sorgusunu kimlik sırasına bağlamayın; gerçek bir zaman sütunu ve gerekiyorsa eşitliği bozan ikinci anahtar kullanın.

UUIDv7'nin zaman sızdırdığını unutmak. Kimliği gören herkes kaydın ne zaman oluşturulduğunu okuyabilir. Hesap açılış tarihi ya da belgenin oluşturulma anı hassassa, o kayıtlarda dışarıya v4 gösterin.

Tahmin edilemez kimliği yetkilendirme sanmak. Rastgele kimlik, başka kullanıcının kaydına ulaşmayı zorlaştırır ama engellemez: kimlikler loglara, paylaşılan bağlantılara, tarayıcı geçmişine düşer. Her istekte kaydın isteği yapana ait olduğunu sunucuda denetleyin. Kimliğin biçimi bir güvenlik katmanı değildir.

Kısa kimlik için tamsayıyı kodlamak. Ardışık tamsayıyı kısa bir metne çeviren kütüphaneler gizleme sağlar, şifreleme sağlamaz. Alfabe ve parametreler bilinirse kodlama geri çözülür. Hacmi gizlemek amaçsa bu yöntem yeterli değildir.

Var olan bir sistemi değiştirmek

Çalışan bir tabloda birincil anahtarı değiştirmek yerine çoğu zaman yeni bir sütun eklemek yeter. Tabloya varsayılan değeri olan bir uuid sütunu ekleyin, eski satırları küçük gruplar hâlinde doldurun, eşzamanlı olarak benzersiz indeks oluşturun ve dış dünyaya bu sütunu göstermeye başlayın. İç yabancı anahtarlar bigint olarak kalabilir. Böylece tek bir büyük kilit ve tablo yeniden yazımı olmadan dışarıya gösterilen kimliği değiştirmiş olursunuz.

Ne zaman bunlarla uğraşmamalı

Birkaç yüz bin satırı geçmeyecek, belleğe rahatça sığan tablolarda v4 ile v7 arasındaki yazma farkını ölçemezsiniz. Orada dışarıya ne göstereceğinize ve sistemler arası taşıma ihtiyacına bakın, performans gerekçesiyle karar vermeyin. Var olan ve sorunsuz çalışan bir şemayı sırf "modern" olsun diye UUIDv7'ye taşımak da göç riskine değmez; sorun ölçülmeden anahtar değiştirilmez.