PostgreSQL duplicate key value violates unique constraint hatası, eklenen veya güncellenen bir satırın benzersiz olması gereken değerinin mevcut bir değerle çakıştığını gösterir. Hatayı çözmek için önce hangi veritabanı kuralının ihlal edildiğini ve çakışan değerin nasıl oluştuğunu belirlemelisin.
Hata mesajındaki kısıt ve değer nasıl okunur?

Hata mesajındaki ERROR satırı ihlal edilen kısıtın adını, DETAIL satırı ise genellikle çakışan sütunu ve değeri gösterir. Kısıt, verinin uyması gereken bir veritabanı kuralıdır.
Aşağıdaki örneği ayrı bir oturumda çalıştırabilirsin. Geçici users tablosundaki id birincil anahtarı, her satırı benzersiz ve boş olmayan bir değerle tanımlar. email sütunundaki UNIQUE kısıtı ise e-posta değerlerinin tekrarlanmasını engeller.
CREATE TEMP TABLE users (
id SERIAL PRIMARY KEY,
email TEXT UNIQUE
);
INSERT INTO users (email) VALUES ('[email protected]');
INSERT INTO users (email) VALUES ('[email protected]');
İkinci ekleme, İngilizce hata mesajı kullanılan bir ortamda şu hatayı üretir:
ERROR: duplicate key value violates unique constraint "users_email_key"
DETAIL: Key (email)=([email protected]) already exists.
users_email_key, ihlal edilen kısıtın adıdır. DETAIL satırı da email sütunundaki [email protected] değerinin zaten bulunduğunu belirtir. Mesajın dili ortamın yapılandırmasına göre değişebilir.
Birincil anahtar satırı tanımlarken bir tabloda başka sütunlar için de UNIQUE kısıtları bulunabilir. Hatayı susturmak için bu kuralları kaldırmak, yinelenen kayıtların oluşmasına ve veri bütünlüğünün bozulmasına yol açabilir.
ID dizisi geride kaldığında nasıl düzeltilir?
ID üreten dizi tablodaki değerlerin gerisinde kaldığında, önce dizinin durumunu ve tablodaki en büyük ID değerini karşılaştırmalısın. Dizi, sıralı değerler üreten bir veritabanı nesnesidir. ID değerleri elle girildiğinde veya veriler içe aktarıldığında, ilgili dizi bu değerlerle uyumsuz kalabilir.
PostgreSQL, SERIAL ve IDENTITY sütunları için ilişkili bir dizi kullanır. pg_get_serial_sequence işlevi, bu sütunla ilişkili dizinin adını bulur.
Örneği yeni bir oturumda çalıştır: Böylece önceki geçici tablo yerine boş bir tabloyla başlarsın. ID değerini elle ekledikten sonra otomatik ID isteyen sorgu aynı değere denk gelir:
CREATE TEMP TABLE users (
id SERIAL PRIMARY KEY,
email TEXT UNIQUE
);
INSERT INTO users (id, email)
VALUES (1, '[email protected]');
INSERT INTO users (email)
VALUES ('[email protected]');
Varsayılan dizi ayarlarıyla ikinci ekleme şu hatayı üretir:
ERROR: duplicate key value violates unique constraint "users_pkey"
DETAIL: Key (id)=(1) already exists.
Teşhis için hata mesajındaki kısıtın users_pkey olduğunu kontrol et. Ardından tablonun en büyük ID değerine, ilişkili dizinin adına ve bu örnekteki dizinin durumuna bak:
SELECT MAX(id) FROM users;
SELECT pg_get_serial_sequence('users', 'id');
SELECT last_value, is_called FROM users_id_seq;
Bu örnekte en büyük ID 1'dir. Elle yapılan ekleme diziyi ilerletmediği için sonraki otomatik ekleme aynı ID'yi kullanmaya çalışmıştır. Gerçek bir tabloda dizi adını ilk sorgunun sonucuna göre kontrol etmelisin.
Tablo doluyken ve dizi durumunun geri alınmayacağı kontrol edilmişken, diziyi en büyük ID değerine ayarlayabilirsin:
SELECT setval(pg_get_serial_sequence('users', 'id'), MAX(id))
FROM users;
INSERT INTO users (email)
VALUES ('[email protected]')
RETURNING id, email;
setval bu örnekte 1 döndürür; sonraki ekleme ise id değeri 2 olan satırı üretir. İki parametreli setval, is_called değerini true yapar; böylece sonraki dizi çağrısı, ayarlanan değerden ilerler.
setval değerini tablodaki en büyük ID'den küçük seçmek yeniden çakışmaya yol açabilir. Dizi zaten tablonun ilerisindeyse onu gereksiz yere geriye taşıma; özellikle başka oturumlar da değer kullanıyorsa, ayarlama sırasında eş zamanlı yazmaları güvenli biçimde yönet.
Boş bir tabloda MAX(id) boş sonuç verdiğinden bu sorgu ayrı ele alınmalıdır. Ayrıca setval işlevinin üçüncü parametresi false olduğunda sonraki dizi çağrısı ilerlemek yerine verilen değerin kendisini döndürür; başlangıç değerini seçerken bu davranışı hesaba katmalısın.
ON CONFLICT ile tekrar eden kayıt nasıl yönetilir?

ON CONFLICT, eklenmek istenen satır belirlenen bir benzersizlik kuralıyla çakıştığında kaydı atlamanı veya mevcut satırı güncellemeni sağlar. Hangi davranışın uygun olduğu, tekrar gelen verinin iş kuralında ne anlama geldiğine bağlıdır.
Eklemeden önce SELECT ile kayıt aramak tek başına eş zamanlı çakışmaları önlemez. İki işlem de aynı anda kaydı bulamayabilir ve ardından aynı değeri eklemeye çalışabilir. Benzersiz kısıt ve ON CONFLICT davranışı bu kontrolü ekleme işlemi sırasında uygulamana yardımcı olur.
Aşağıdaki örnekleri ayrı bir test ortamında çalıştırabilirsin. Geçici users tablosunda email sütunu benzersizdir; başlangıçta bir kullanıcı bulunur.
CREATE TEMP TABLE users (
email TEXT UNIQUE,
name TEXT NOT NULL
);
INSERT INTO users (email, name)
VALUES ('[email protected]', 'Ada');
Mevcut kaydı korumak için DO NOTHING
Tekrar gelen e-posta adresinde mevcut adın korunması isteniyorsa DO NOTHING kullanabilirsin:
INSERT INTO users (email, name)
VALUES ('[email protected]', 'Deniz')
ON CONFLICT (email) DO NOTHING;
SELECT email, name FROM users;
Çakışan satır eklenmez ve tablodaki ad değişmez. Sorgunun beklenen sonucu şöyledir:
email | name
-----------------+-----
[email protected] | Ada
Mevcut veriyi değiştirmek için DO UPDATE
İş kuralı, aynı e-posta adresiyle gelen yeni adın mevcut kullanıcı kaydına yazılmasını gerektiriyorsa DO UPDATE kullanılabilir. EXCLUDED.name, eklenmesi önerilen satırdaki name değerini belirtir.
INSERT INTO users (email, name)
VALUES ('[email protected]', 'Deniz')
ON CONFLICT (email)
DO UPDATE SET name = EXCLUDED.name;
SELECT email, name FROM users;
Bu işlemden sonra beklenen sonuçta mevcut satırın adı güncellenmiştir:
email | name
-----------------+------
[email protected] | Deniz
ON CONFLICT (email) ifadesindeki hedef, uygun bir benzersiz kısıt veya benzersiz indeksle eşleşmelidir. Böyle bir eşleşme bulunmayan bir sütunu hedef olarak seçmek hata oluşturur; ayrıca başka bir kısıttan kaynaklanan çakışma bu hedefle otomatik olarak çözülmez. Ayrıntıları PostgreSQL INSERT dokümantasyonunda inceleyebilirsin.
Aynı istek iki kez gönderildiğinde ne yapılabilir?
Aynı isteğin tekrar işlenmesini incelerken istemcideki yinelenen gönderimleri, başarısız olduğu düşünülen isteklerin yeniden denenmesini ve aynı kaydın paralel işlemler tarafından ele alınmasını kontrol edebilirsin. Tekrarın hangi aşamada oluştuğunu anlamak, veritabanındaki davranışı iş kuralına göre seçmene yardımcı olur.
- İstek kayıtlarını incele: Aynı işlem için benzer içeriğe sahip birden fazla istek gelip gelmediğine bak.
- İşlem kayıtlarını karşılaştır: İsteklerin hangi aşamada işlendiğini ve çakışan değerin nerede üretildiğini araştır.
- Tekrarın kaynağını belirle: İstemcide birden fazla gönderim, yeniden deneme veya paralel işlem olup olmadığını kontrol et.
- Benzersizlik kuralını değerlendir: E-posta gibi bir alanın mı, yoksa işleme ait ayrı bir anahtarın mı tekrarları tanımlaması gerektiğini belirle.
- İdempotency yaklaşımını değerlendir: Aynı işlem için kullanılan bir anahtarla tekrar gelen isteğin önceki sonucunu ilişkilendirmeyi ve yan etkilerin yeniden oluşmasını önlemeyi planla.
Veritabanındaki benzersiz kısıtı korurken, tekrarın mevcut kaydı değiştirmemesi gerekiyorsa DO NOTHING uygun olabilir. Yeni verinin mevcut kayda uygulanması isteniyorsa DO UPDATE değerlendirilebilir; ancak bir satırı güncellemek, uygulamadaki diğer işlemlerin de kendiliğinden tekrarsız çalışacağı anlamına gelmez.
Sık Sorulan Sorular
Benzersiz e-posta hatası ile birincil anahtar hatasını nasıl ayırt edebilirim?
Hata mesajındaki kısıt adına ve çakışan sütunu gösteren ayrıntıya bakarak hangi değerin tekrarlandığını ayırt edebilirsin. email sütununa işaret eden bir ayrıntı e-posta çakışmasını, birincil anahtar sütununa işaret eden ayrıntı ise o anahtarın tekrarlandığını gösterir; kısıt adını tablo tanımıyla karşılaştırmak da yardımcı olur.
setval çalıştırdıktan sonra neden tekrar duplicate key hatası alabilirim?
setval sonrasında hata sürüyorsa çakışma başka bir benzersiz sütunda olabilir veya yanlış dizi ya da değer ayarlanmış olabilir. Elle verilen kimlikleri, eş zamanlı eklemeleri ve setval çağrısındaki is_called değerini de kontrol et; bu değer sonraki nextval sonucunu etkiler.
Hata mesajındaki kısıtı belirledikten sonra tekrarın kaynağını ve beklenen uygulama davranışını birlikte değerlendirerek uygun düzeltmeyi seçebilirsin.