PostgreSQL her hata mesajına SQL standardına uyan beş karakterlik bir SQLSTATE kodu verir: 23505 benzersizlik ihlali (duplicate key), 42P01 tablo bulunamadı, 42501 yetki yok, 23503 yabancı anahtar ihlali, 22P02 geçersiz girdi sözdizimi, 40001 serileştirme hatası, 40P01 deadlock. İlk iki karakter hata sınıfını, son üç karakter sınıf içindeki durumu belirtir. Uygulamanız hatayı mesaj metninden değil bu koddan tanımalıdır; kılavuza göre kodlar sürümler arasında daha az değişir ve mesajların çevrilmesinden etkilenmez.
Bu yazıda en sık karşılaşılan hataların her birini PostgreSQL 18.1 üzerinde gerçekten ürettik, çıktıları olduğu gibi gösterdik ve her birinin nedenini ve çözümünü anlattık. Mesajlar İngilizcedir, çünkü test sunucusu lc_messages=C ile çalışıyordu; Türkçe açıklamalar bize aittir. Bağlantı kurulamadığında görülen hatalar ayrı bir konudur ve bağlantı hataları yazısında anlatılıyor. Serinin tamamı için PostgreSQL öğrenme rehberine bakabilirsiniz.
SQLSTATE nasıl okunur?

Kılavuzun Ek A bölümündeki tablo PostgreSQL 18.6'da tanımlı tüm kodları listeler. Her sınıfın son üç karakteri 000 olan bir "genel" kodu vardır ve daha özel bir kod atanmamış durumlar için kullanılır. Bu yüzden belirli bir kodu tanımayan bir uygulama bile ilk iki karaktere bakarak ne yapacağına karar verebilir. Tablodaki "Condition Name" sütunu PL/pgSQL'de kullanılacak adı verir; örneğin 23505 için unique_violation. En sık işinize yarayacak sınıflar:
| Sınıf | Anlamı | Örnek kodlar |
|---|---|---|
| 08 | Bağlantı istisnası | 08006 connection_failure, 08001 |
| 22 | Veri istisnası | 22P02 invalid_text_representation, 22012 division_by_zero, 22001 |
| 23 | Bütünlük kısıtı ihlali | 23502, 23503, 23505, 23514 |
| 25 | Geçersiz transaction durumu | 25P02 in_failed_sql_transaction |
| 28 | Geçersiz yetkilendirme | 28P01 invalid_password |
| 40 | Transaction geri alındı | 40001 serialization_failure, 40P01 deadlock_detected |
| 42 | Sözdizimi hatası veya erişim kuralı ihlali | 42601, 42501, 42703, 42883, 42P01, 42P07 |
| 53 | Yetersiz kaynak | 53300 too_many_connections |
| 55 | Nesne gerekli durumda değil | 55P03 lock_not_available |
| 57 | Operatör müdahalesi | 57014 query_canceled |
| P0 | PL/pgSQL hatası | P0001 raise_exception |
psql'de kodu ve ayrıntıları görmek
psql varsayılan olarak SQLSTATE kodunu göstermez. VERBOSITY değişkeni default, verbose, terse veya sqlstate değerlerini alır. verbose kodu, ilgili şema, tablo ve kısıt adlarını ve hatanın sunucu kaynağındaki yerini gösterir:
CREATE TABLE musteri (id integer PRIMARY KEY, eposta text UNIQUE NOT NULL, ad text);
CREATE TABLE siparis (id integer PRIMARY KEY,
musteri_id integer REFERENCES musteri(id),
tutar numeric CHECK (tutar > 0));
INSERT INTO musteri VALUES (1, 'ayse@ornek.com', 'Ayşe');
\set VERBOSITY verbose
INSERT INTO musteri VALUES (2, 'ayse@ornek.com', 'Ayşe Kaya');ERROR: 23505: duplicate key value violates unique constraint "musteri_eposta_key"
DETAIL: Key (eposta)=(ayse@ornek.com) already exists.
SCHEMA NAME: public
TABLE NAME: musteri
CONSTRAINT NAME: musteri_eposta_key
LOCATION: _bt_check_unique, nbtinsert.c:673Aynı komut \set VERBOSITY sqlstate ile yalnızca ERROR: 23505 satırını basar. Bir hatayı az önce varsayılan ayrıntıda gördüyseniz \errverbose son hatayı en yüksek ayrıntıda tekrarlar. Kılavuz, ayrı alanlarda gelen şema, tablo, sütun ve kısıt adlarının uygulamaların çevrilmiş olabilecek mesaj metnini ayrıştırmak zorunda kalmaması için sağlandığını belirtir; bu kapsamın tam olarak yalnızca sınıf 23 için garanti edildiğini de not eder. Sürücünüz (libpq, PDO, JDBC) bu alanları ayrı ayrı okumanıza izin verir; PHP tarafı için PHP PDO ile veritabanı bağlantısı yazısına bakın. psql değişkenleri psql komutları yazısında.
Sınıf 23: bütünlük kısıtı ihlalleri
23505 unique_violation: duplicate key
Birincil anahtar veya UNIQUE kısıtı olan sütuna var olan bir değeri yazmaya çalıştığınızda oluşur. DETAIL satırı çakışan anahtarı, CONSTRAINT NAME alanı kısıtın adını verir. Çözüm duruma göre değişir: değer gerçekten tekrar ediyorsa uygulama kullanıcıya anlamlı bir mesaj göstermelidir; "varsa güncelle, yoksa ekle" istiyorsanız INSERT ... ON CONFLICT kullanın (bkz. UPSERT ve MERGE). Kimlik sütunu bir sequence'ten geliyorsa ve elle değer eklenmişse sequence geride kalmış olabilir; bu durum identity ve sequence yazısında anlatılıyor.
Kılavuzun serileştirme hatası sayfası ilginç bir noktaya değinir: uygulama mevcut anahtarlara bakıp yeni bir anahtar seçtiyse ve aynı anda başka bir oturum aynı anahtarı seçtiyse ortaya çıkan 23505 aslında bir serileştirme sorunudur ve yeniden denemek uygun olabilir. Ancak 23505 kalıcı bir veri sorununu da gösterebileceği için 40001 gibi koşulsuz yeniden denenmemelidir.
23503 foreign_key_violation
İki yönde ortaya çıkar. Birincisi, alt tabloya üst tabloda olmayan bir anahtar yazmaktır:
INSERT INTO siparis VALUES (10, 99, 100);ERROR: insert or update on table "siparis" violates foreign key constraint "siparis_musteri_id_fkey"
DETAIL: Key (musteri_id)=(99) is not present in table "musteri".İkincisi, hâlâ başvurulan bir üst satırı silmek ya da anahtarını değiştirmektir:
INSERT INTO siparis VALUES (10, 1, 100);
DELETE FROM musteri WHERE id = 1;ERROR: update or delete on table "musteri" violates foreign key constraint "siparis_musteri_id_fkey" on table "siparis"
DETAIL: Key (id)=(1) is still referenced from table "siparis".Çözüm, verinin sırasını düzeltmek (önce üst satırı ekleyin, önce alt satırları silin) ya da ilişki için bilinçli bir ON DELETE CASCADE, SET NULL veya RESTRICT davranışı seçmektir. Seçenekler primary key, foreign key ve kısıtlar yazısında.
23502 not_null_violation ve 23514 check_violation
NOT NULL sütuna değer verilmediğinde ya da CHECK koşulu sağlanmadığında oluşur. İkisinde de DETAIL satırı reddedilen satırın tamamını gösterir; hangi sütunun eksik olduğunu buradan görürsünüz:
INSERT INTO musteri (id, ad) VALUES (3, 'Ali');
INSERT INTO siparis VALUES (11, 1, -5);ERROR: null value in column "eposta" of relation "musteri" violates not-null constraint
DETAIL: Failing row contains (3, null, Ali).
ERROR: new row for relation "siparis" violates check constraint "siparis_tutar_check"
DETAIL: Failing row contains (11, 1, -5).Sınıf 22: veri istisnaları

22P02 invalid_text_representation
Bir metni hedef tipe çeviremediğinde oluşur. Klasik nedenler: sayısal sütuna boş metin göndermek, formdan gelen "abc" değerini tamsayı parametresine bağlamak, UUID sütununa bozuk değer yazmak. Mesaj, sorunlu değeri ve satırdaki konumunu gösterir:
SELECT * FROM siparis WHERE id = '';ERROR: invalid input syntax for type integer: ""
LINE 1: SELECT * FROM siparis WHERE id = '';
^Boş metin NULL değildir; boş girdiyi uygulamada NULL'a çevirin ya da sunucu tarafında NULLIF(deger, '') kullanın. Girdiyi her zaman parametre olarak bağlayın; metin birleştirerek sorgu kurmak hem bu tür hatalara hem de SQL injection riskine yol açar. Tiplerin kabul ettiği girdi biçimleri veri tipleri yazısında.
22012 division_by_zero ve 22001
SELECT 1/0; sorgusu ERROR: division by zero verir; \set VERBOSITY sqlstate ile çıktı ERROR: 22012 olur. Paydanın sıfır olabileceği hesaplarda NULLIF(payda, 0) ile sonucu NULL yapabilirsiniz. Aynı sınıftaki 22001 (string_data_right_truncation) ise varchar(n) sınırını aşan metinde görülür: varchar(3) sütununa 'abcd' eklemek ERROR: 22001: value too long for type character varying(3) verdi. Dikkat: açık dönüşüm farklı davranır; SELECT 'uzun metin'::varchar(3) hata vermeden uzu döndürdü.
Sınıf 42: sözdizimi ve erişim kuralı hataları
42P01 undefined_table: relation does not exist
En sık görülen nedenler: tablo gerçekten yok, tablo başka bir şemada ve search_path içinde değil, ya da büyük/küçük harf sorunu. Kılavuzun sözcük yapısı bölümüne göre tırnaksız tanımlayıcılar küçük harfe katlanır, çift tırnaklı tanımlayıcılar ise olduğu gibi kalır. Tabloyu "Urun" diye tırnakla oluşturduysanız tırnaksız Urun yazmak urun aranmasına yol açar:
CREATE TABLE "Urun" (id int);
SELECT * FROM Urun;ERROR: relation "urun" does not exist
LINE 1: SELECT * FROM Urun;
^Mesajdaki adın küçük harfle yazıldığına dikkat edin; bu, katlamanın gerçekleştiğinin ipucudur. SELECT * FROM "Urun"; çalışır. Kalıcı çözüm, tablo ve sütun adlarında tırnak gerektirmeyen küçük harfli adlar kullanmaktır. Şema ve search_path kaynaklı durumlar için veritabanı ve şema oluşturma yazısına bakın; psql'de \dt *.* ile tüm şemalardaki tabloları listeleyebilirsiniz.
42703, 42601 ve 42883
42703 undefined_column olmayan bir sütun adında görülür (column "soyad" does not exist); yine büyük/küçük harf ve tırnak kuralı sık nedendir. 42601 syntax_error yazım hatasında ortaya çıkar ve LINE satırındaki işaret hatanın başladığı yeri gösterir: SELEC 1; için mesaj syntax error at or near "SELEC" oldu. 42883 undefined_function verilen ad ve argüman tipleriyle eşleşen fonksiyon olmadığında gelir:
SELECT length(42);ERROR: function length(integer) does not exist
LINE 1: SELECT length(42);
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.İpucu satırının dediği gibi argümanı uygun tipe çevirin: length(42::text). Bu hata bir eklentinin fonksiyonunu çağırırken de görülür; eklenti o veritabanında kurulu olmayabilir.
42501 insufficient_privilege: permission denied
Oturumun rolü nesne üzerinde gerekli yetkiye sahip değildir. Yetkisiz bir rolle tabloyu okumayı denediğimizde:
CREATE ROLE okuyucu;
SET ROLE okuyucu;
SELECT * FROM musteri;ERROR: permission denied for table musteriÇözüm, gereken en küçük yetkiyi vermektir; örneğin GRANT SELECT ON musteri TO okuyucu;. Şema üzerinde USAGE yetkisi de gerekebilir. Ayrıntılar GRANT ve REVOKE, rol yapısı kullanıcı ve rol yönetimi, satır düzeyinde kısıtlama row level security yazısında. Sorunu süper kullanıcıya geçerek çözmek, uygulamaya gereğinden fazla yetki vermek demektir.
25P02: transaction iptal edildi, komutlar yok sayılıyor
Bir transaction bloğu içinde herhangi bir komut hata verdiğinde, PostgreSQL o bloğun geri kalanındaki komutları çalıştırmaz ve blok bitene kadar her komuta aynı hatayı döndürür:
BEGIN;
SELECT 1/0;
SELECT 1;
ROLLBACK;BEGIN
ERROR: division by zero
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACKBu hata asıl sorun değildir; asıl sorun ondan önceki ilk hatadır ve log ya da uygulama çıktısında onu aramanız gerekir. Çıkış yolu ROLLBACK, hatanın öncesine dönmek istiyorsanız SAVEPOINT ve ROLLBACK TO SAVEPOINT'tir. psql'in ON_ERROR_ROLLBACK değişkeni etkileşimli kullanımda bunu otomatikleştirir. Konu BEGIN, COMMIT ve ROLLBACK yazısında ayrıntılı.
Sınıf 40: yeniden denenmesi gereken hatalar

40001 serialization_failure
REPEATABLE READ ve SERIALIZABLE izolasyon seviyeleri, serileştirme anomalilerini önlemek için bilerek hata üretir. Örnekte birinci oturum REPEATABLE READ transaction'ı içinde bir satırı okudu ve beklerken ikinci oturum aynı satırı güncelleyip commit etti; birinci oturum satırı güncellemeye çalışınca:
-- 1. oturum
\set VERBOSITY verbose
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT bakiye FROM hesap WHERE id = 1;
-- (bu sırada 2. oturum: UPDATE hesap SET bakiye = bakiye + 10 WHERE id = 1;)
UPDATE hesap SET bakiye = bakiye - 100 WHERE id = 1;ERROR: 40001: could not serialize access due to concurrent update
LOCATION: ExecUpdate, nodeModifyTable.c:2585Kılavuz bu seviyeleri kullanan uygulamaların serileştirme hatası alan transaction'ları yeniden denemeye hazır olması gerektiğini söyler. Mesaj metni duruma göre değişir ama kod her zaman 40001'dir. Önemli kural: yalnızca son komutu değil, hangi SQL'in çalışacağına ve hangi değerlerin kullanılacağına karar veren mantık dahil tüm transaction yeniden denenmelidir. PostgreSQL bu yüzden otomatik yeniden deneme sunmaz. Yüksek çekişmede birden fazla deneme gerekebilir. Ayrıntı izolasyon seviyeleri ve MVCC yazısında.
40P01 deadlock_detected
İki transaction birbirinin tuttuğu kilidi beklediğinde PostgreSQL döngüyü fark eder ve birini iptal eder. İki oturum hesap 1 ve 2'yi ters sırada güncellediğinde bir taraf şu hatayı aldı, diğeri başarıyla commit etti:
ERROR: 40P01: deadlock detected
DETAIL: Process 33264 waits for ShareLock on transaction 824; blocked by process 56000.
Process 56000 waits for ShareLock on transaction 823; blocked by process 33264.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,2) in relation "hesap"
LOCATION: DeadLockReport, deadlock.c:1138Kılavuz deadlock hatalarının da yeniden denenmesini önerilebilir sayar. Kalıcı çözüm ise satırları her transaction'da aynı sırayla (örneğin id sırasıyla) kilitlemektir. Kilit modları ve pg_locks ile izleme kilitler ve deadlock yazısında.
| Kod | Koşul adı | Tipik neden | İlk çözüm |
|---|---|---|---|
| 23505 | unique_violation | Var olan anahtar tekrar yazıldı | ON CONFLICT veya kullanıcıya mesaj |
| 23503 | foreign_key_violation | Olmayan üst satır ya da başvurulan satırı silmek | Sırayı düzelt, ON DELETE davranışı seç |
| 23502 | not_null_violation | Zorunlu sütun boş | Değer veya DEFAULT ver |
| 22P02 | invalid_text_representation | Tipe çevrilemeyen metin | Girdiyi doğrula, NULLIF kullan |
| 42P01 | undefined_table | Tablo yok, search_path, tırnak | Şema adı ve harf durumunu kontrol et |
| 42501 | insufficient_privilege | Yetki eksik | Gereken GRANT |
| 25P02 | in_failed_sql_transaction | Önceki hata bloğu iptal etti | ROLLBACK, ilk hatayı bul |
| 40001 | serialization_failure | Eşzamanlı güncelleme | Tüm transaction'ı yeniden dene |
| 40P01 | deadlock_detected | Ters sırada kilit | Yeniden dene, kilit sırasını sabitle |
PL/pgSQL içinde kodla yakalamak
Fonksiyon ve DO bloklarında hataları koşul adıyla yakalayabilirsiniz; SQLSTATE değişkeni yakalanan kodu verir:
DO $$
BEGIN
INSERT INTO musteri VALUES (5, 'ayse@ornek.com', 'Ayşe Demir');
EXCEPTION WHEN unique_violation THEN
RAISE NOTICE 'Bu e-posta zaten kayıtlı (SQLSTATE %)', SQLSTATE;
END
$$;NOTICE: Bu e-posta zaten kayıtlı (SQLSTATE 23505)
DOKılavuz koşul adlarının büyük veya küçük harfle yazılabileceğini ve PL/pgSQL'in uyarı sınıflarının (00, 01, 02) adlarını tanımadığını belirtir. GET STACKED DIAGNOSTICS, RAISE ile kendi kodunuzu üretmek ve performans etkileri PL/pgSQL hata yakalama yazısında.
Bağlantı ve kimlik doğrulama hataları
Sınıf 08 (bağlantı istisnası), 28 (28P01 invalid_password) ve 53300 too_many_connections sorgu çalışmadan önce, bağlantı aşamasında ortaya çıkar. "Connection refused", "password authentication failed" ve "no pg_hba.conf entry" mesajlarının nedenleri ve teşhis adımları bağlantı hataları ve connection refused yazısında.
Sık yapılan hatalar
- Hata mesajı metnini ayrıştırmak: mesajlar sunucunun
lc_messagesayarına göre çevrilebilir; kodu kullanın. - 25P02'yi asıl hata sanmak: önceki ilk hatayı arayın.
- 40001 aldığında yalnızca başarısız komutu tekrarlamak: tüm transaction mantığını baştan çalıştırın.
- 23505'i her zaman yeniden denemek: aynı veriyle tekrar denemek aynı hatayı verir; kılavuz bu kodda dikkatli olunmasını ister.
- Yetki hatasını süper kullanıcıyla bağlanarak geçiştirmek.
Sık Sorulan Sorular
Tüm kodların tam listesi nerede?
Kılavuzun "Appendix A. PostgreSQL Error Codes" sayfasında; tablo PostgreSQL 18.6'da tanımlı tüm kodları ve PL/pgSQL koşul adlarını içerir. Bazı kodlar SQL standardında tanımlı olduğu için listede yer alır ama şu an kullanılmaz.
Hata mesajlarını Türkçe görebilir miyim?
Kılavuza göre mesaj çevirisi derleme sırasında NLS desteği seçildiyse ve lc_messages uygun ayarlandıysa mümkündür. Ancak aynı kılavuz, uygulamaların mesaj metnine değil hata koduna güvenmesini önerir; çeviri açıkken bile kod aynı kalır.
Hata hangi sorgudan geldi, nasıl bulurum?
Deadlock örneğindeki ipucu satırı ayrıntıların sunucu logunda olduğunu söyler. Log ayarları ve uzun süren sorguları bulma pg_stat_activity ve pg_stat_statements yazısında.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: PostgreSQL öğrenme rehberi · Bağlantı hataları · PL/pgSQL hata yakalama · İzolasyon seviyeleri · Kilitler ve deadlock · SQL nedir