PostgreSQL'de bir transaction'ın, aynı anda çalışan diğer transaction'ların değişikliklerinden ne kadarını göreceğini izolasyon seviyesi belirler. Varsayılan seviye READ COMMITTED'tır: her komut, kendisi başladığı ana kadar commit edilmiş verinin anlık görüntüsünü görür. REPEATABLE READ tüm transaction boyunca tek bir görüntü kullanır, SERIALIZABLE ise buna ek olarak, transaction'lar sırayla çalışsaydı oluşamayacak sonuçları tespit edip bir transaction'ı 40001 hatasıyla geri alır.

Bu yazıda önce bu davranışın temelindeki MVCC mekanizmasını gerçek satır sürümleriyle gösteriyor, sonra her seviyeyi iki ayrı psql oturumu aynı anda çalıştırılarak üretilmiş çıktılarla inceliyoruz. Son bölümde serialization hatalarında transaction'ı nasıl yeniden deneyeceğinizi ve seviye seçerken nelere bakacağınızı özetliyoruz. Transaction bloğunun temel komutları için BEGIN, COMMIT ve ROLLBACK yazısına bakabilirsiniz.

MVCC: okuma yazmayı, yazma okumayı engellemez

PostgreSQL eşzamanlılığı çok sürümlü eşzamanlılık denetimi (Multiversion Concurrency Control, MVCC) ile yönetir. Kılavuzun tarifiyle her SQL komutu verinin bir süre önceki hâlinin anlık görüntüsünü (snapshot) görür; tablodaki o anki durum ne olursa olsun bu görüntü tutarlıdır. Bu modelin en büyük kazancı, veri okumak için alınan kilitlerin veri yazmak için alınan kilitlerle çakışmamasıdır: okuyan bir sorgu yazanı, yazan bir komut da okuyanı bekletmez.

Bunu mümkün kılan, bir satır güncellendiğinde yerinde değiştirilmemesi, yeni bir satır sürümünün yazılmasıdır. Her satırda gizli sistem sütunları bulunur: xmin satır sürümünü ekleyen transaction'ın kimliği, xmax onu silen ya da güncelleyen transaction'ın kimliği, ctid ise sürümün tablodaki fiziksel konumudur. Küçük bir stok tablosunda güncelleme öncesi ve sonrasına bakalım:

sql
CREATE TABLE stok (urun text PRIMARY KEY, adet int NOT NULL);
INSERT INTO stok VALUES ('çay', 10), ('kahve', 5);
SELECT xmin, xmax, ctid, * FROM stok;
UPDATE stok SET adet = 9 WHERE urun = 'çay';
SELECT xmin, xmax, ctid, * FROM stok;
text
 xmin | xmax | ctid  | urun  | adet
------+------+-------+-------+------
  959 |    0 | (0,1) | çay   |   10
  959 |    0 | (0,2) | kahve |    5
(2 rows)

UPDATE 1
 xmin | xmax | ctid  | urun  | adet
------+------+-------+-------+------
  959 |    0 | (0,2) | kahve |    5
  960 |    0 | (0,3) | çay   |    9
(2 rows)

Güncellemeden sonra "çay" satırı yeni bir konuma, (0,3)'e taşındı ve xmin değeri güncellemeyi yapan transaction'ın kimliği oldu. (0,1)'deki eski sürüm hâlâ dosyada duruyor; artık hiçbir transaction onu görmeyecek duruma geldiğinde VACUUM tarafından temizlenir. Bu temizliğin neden gerekli olduğu ve ihmal edilirse ne olacağı VACUUM, autovacuum ve bloat yazısında anlatılıyor. Sizin göreceğiniz transaction numaraları elbette farklı olacaktır.

Standardın tanımladığı anomaliler ve PostgreSQL'in seviyeleri

Standardın tanımladığı anomaliler ve PostgreSQL'in seviyeleri: Dirty read (kirli okuma), Nonrepeatable read (tekrarlanamayan okuma), Phantom read (hayalet satır), Serialization...

SQL standardı izolasyon seviyelerini, eşzamanlı transaction'lar arasında hangi olguların yaşanmasına izin verildiğine göre tanımlar:

  • Dirty read (kirli okuma): Bir transaction, henüz commit edilmemiş başka bir transaction'ın yazdığı veriyi okur.
  • Nonrepeatable read (tekrarlanamayan okuma): Daha önce okunan bir satır yeniden okunduğunda, arada commit edilen bir değişiklik yüzünden farklı çıkar.
  • Phantom read (hayalet satır): Aynı koşulla yapılan sorgu ikinci kez çalıştırıldığında, arada commit edilen ekleme veya silme nedeniyle farklı bir satır kümesi döner.
  • Serialization anomaly: Bir grup transaction'ın başarılı commit sonucu, onları herhangi bir sırayla tek tek çalıştırmanın vereceği sonuçların hiçbiriyle uyuşmaz.
SeviyeDirty readNonrepeatable readPhantom readSerialization anomaly
READ UNCOMMITTEDStandartta serbest, PostgreSQL'de olmazOlabilirOlabilirOlabilir
READ COMMITTED (varsayılan)OlmazOlabilirOlabilirOlabilir
REPEATABLE READOlmazOlmazStandartta serbest, PostgreSQL'de olmazOlabilir
SERIALIZABLEOlmazOlmazOlmazOlmaz

Tablodaki iki ayrıntı önemlidir. Birincisi, PostgreSQL dört seviyeyi de kabul eder ama içeride yalnızca üç farklı davranış uygular: READ UNCOMMITTED istediğinizde READ COMMITTED gibi çalışır. İkincisi, PostgreSQL'in REPEATABLE READ uygulaması standardın istediğinden daha güçlüdür ve hayalet satırlara izin vermez. Standart yalnızca her seviyede olmaması gerekenleri tanımladığı için bu fazlalık standarda uygundur.

Seviyeyi seçmek ve görmek

İzolasyon seviyesini transaction başlarken BEGIN ISOLATION LEVEL ... ile ya da blok içinde ilk sorgudan önce SET TRANSACTION ile verirsiniz. Oturumun varsayılanını default_transaction_isolation parametresi belirler; o anki transaction'ın seviyesini transaction_isolation gösterir:

sql
SHOW default_transaction_isolation;
BEGIN ISOLATION LEVEL READ UNCOMMITTED;
SHOW transaction_isolation;
COMMIT;
SET default_transaction_isolation = 'repeatable read';
BEGIN;
SHOW transaction_isolation;
COMMIT;
text
 default_transaction_isolation
-------------------------------
 read committed
(1 row)

BEGIN
 transaction_isolation
-----------------------
 read uncommitted
(1 row)

COMMIT
SET
BEGIN
 transaction_isolation
-----------------------
 repeatable read
(1 row)

COMMIT

SHOW, istediğiniz adı (read uncommitted) gösterir; ancak kılavuza göre davranış READ COMMITTED ile aynıdır. Varsayılanı kalıcı olarak değiştirmek için parametreyi postgresql.conf, ALTER DATABASE ... SET veya ALTER ROLE ... SET ile ayarlayabilirsiniz; yöntemler yapılandırma yazısında.

READ COMMITTED: her komut yeni bir görüntü alır

Aşağıdaki denemelerde iki psql oturumunu aynı anda başlattık ve sırayı pg_sleep ile ayarladık. A oturumu bir transaction içinde aynı satırı iki kez okuyor; B oturumu arada, birinci saniyede, değeri güncelleyip commit ediyor (autocommit).

sql
-- Oturum A
BEGIN;
SELECT adet FROM stok WHERE urun = 'kahve';
SELECT pg_sleep(2);
SELECT adet FROM stok WHERE urun = 'kahve';
COMMIT;

-- Oturum B
SELECT pg_sleep(1);
UPDATE stok SET adet = 4 WHERE urun = 'kahve';
text
-- Oturum A
BEGIN
 adet
------
    5
(1 row)

 adet
------
    4
(1 row)

COMMIT

-- Oturum B
UPDATE 1

Oturum çıktılarında pg_sleep satırlarının boş sonuç tablolarını kısalık için çıkardık; geri kalan satırlar psql'in gerçek çıktısıdır. Aynı transaction içindeki iki SELECT farklı sonuç verdi; bu, tekrarlanamayan okumadır ve READ COMMITTED seviyesinde beklenen davranıştır. Kılavuz bunu açıkça yazar: bu seviyede her sorgu, kendisi başladığı anda commit edilmiş verinin görüntüsünü görür.

Güncelleme komutlarında durum biraz daha inceliklidir. UPDATE, DELETE ve SELECT FOR UPDATE hedef satırları komutun başındaki görüntüye göre bulur. Bulunan satırı başka bir transaction o sırada değiştiriyorsa beklenir. Diğer transaction commit ederse, komut satırın yeni sürümü üzerinde WHERE koşulunu yeniden değerlendirir; koşul hâlâ sağlanıyorsa işlemi o sürüme uygular. Kılavuzdaki örneği Türkçe verilerle tekrarladık: iki satırın ziyaret sayısı 9 ve 10. A oturumu bütün satırları bir artırıyor, B ise tam o sırada sayısı 10 olan satırı silmeye çalışıyor:

sql
-- Hazırlık
CREATE TABLE sayfa (ad text, ziyaret int);
INSERT INTO sayfa VALUES ('anasayfa', 9), ('iletisim', 10);

-- Oturum A
BEGIN;
UPDATE sayfa SET ziyaret = ziyaret + 1;
SELECT pg_sleep(2);
COMMIT;

-- Oturum B
SELECT pg_sleep(1);
DELETE FROM sayfa WHERE ziyaret = 10;
SELECT * FROM sayfa ORDER BY ad;
text
-- Oturum B
DELETE 0
    ad    | ziyaret
----------+---------
 anasayfa |      10
 iletisim |      11
(2 rows)

DELETE hiçbir satırı silmedi, oysa hem güncellemeden önce hem sonra değeri 10 olan bir satır vardı. Nedeni şudur: B'nin görüntüsünde "anasayfa" 9 olduğu için baştan elendi; "iletisim" ise 10 olduğu için seçildi, ama A commit ettikten sonra yeniden kontrol edildiğinde değeri 11'e çıkmıştı. Kılavuz bu yüzden READ COMMITTED'ı karmaşık arama koşulları içeren güncellemeler için uygun bulmaz; önceden belirlenmiş tek bir satırı değiştiren havale gibi işlemler için ise tam yerinde bir seviye olarak tanımlar. INSERT ... ON CONFLICT DO UPDATE bu seviyede her satır için ekleme ya da güncellemeden birini garanti eder; bu konu UPSERT ve MERGE yazısında ayrıca anlatılıyor.

REPEATABLE READ: transaction boyunca tek görüntü

REPEATABLE READ seviyesinde görüntü, transaction'daki ilk sorgu ya da veri değiştiren komut başladığında alınır ve blok bitene kadar değişmez. Aynı denemeyi bu seviyede tekrarladık; B bu kez değeri 3 yaptı:

sql
-- Oturum A
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT adet FROM stok WHERE urun = 'kahve';
SELECT pg_sleep(2);
SELECT adet FROM stok WHERE urun = 'kahve';
COMMIT;
text
BEGIN
 adet
------
    4
(1 row)

 adet
------
    4
(1 row)

COMMIT

A, B'nin commit ettiği 3 değerini görmedi; iki okuma da aynı sonucu verdi. Uzun bir raporun bütün sorgularının aynı ana ait tutarlı veriyi görmesi gerektiğinde bu seviye doğal bir seçimdir. Bedeli şudur: bu seviyedeki bir transaction, kendisi başladıktan sonra başkasının değiştirdiği bir satırı güncellemeye kalkarsa hata alır. Aşağıda A, çay stokunu okuyor; B arada aynı satırı güncelliyor; A sonra kendi güncellemesini deniyor:

sql
-- Oturum A
\set VERBOSITY verbose
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT adet FROM stok WHERE urun = 'çay';
SELECT pg_sleep(2);
UPDATE stok SET adet = adet - 1 WHERE urun = 'çay';
ROLLBACK;

-- Oturum B
SELECT pg_sleep(1);
UPDATE stok SET adet = adet - 2 WHERE urun = 'çay';
text
-- Oturum A
BEGIN
 adet
------
    9
(1 row)

ERROR:  40001: could not serialize access due to concurrent update
LOCATION:  ExecUpdate, nodeModifyTable.c:2585
ROLLBACK

\set VERBOSITY verbose sayesinde mesajın başında SQLSTATE kodu (40001) ve sunucu kaynak kodundaki konum (LOCATION) görünüyor. Kılavuzun önerisi nettir: bu hatayı alan uygulama transaction'ı iptal edip en baştan yeniden çalıştırmalıdır. İkinci denemede B'nin değişikliği görüntünün parçası olacağı için mantıksal bir çakışma kalmaz. Yalnızca okuyan transaction'lar bu seviyede hiçbir zaman serialization hatası almaz.

Write skew: REPEATABLE READ'in kaçırdığı anomali

Snapshot izolasyonu her şeyi çözmez. Hastanede en az bir doktorun nöbette kalması gerektiğini düşünün. İki doktor aynı anda "nöbette kaç kişi var?" diye bakıyor, ikisi de 2 görüyor ve kendini nöbetten çıkarıyor. Her transaction tek başına doğru çalıştı, ama sonuç kuralı çiğniyor:

sql
CREATE TABLE nobet (doktor text PRIMARY KEY, gorevde boolean NOT NULL);
INSERT INTO nobet VALUES ('Dr. Aslı', true), ('Dr. Emre', true);

-- Oturum A
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT count(*) FROM nobet WHERE gorevde;          -- 2
SELECT pg_sleep(1);
UPDATE nobet SET gorevde = false WHERE doktor = 'Dr. Aslı';
SELECT pg_sleep(1);
COMMIT;

-- Oturum B
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT count(*) FROM nobet WHERE gorevde;          -- 2
SELECT pg_sleep(1.5);
UPDATE nobet SET gorevde = false WHERE doktor = 'Dr. Emre';
SELECT pg_sleep(1);
COMMIT;

-- İkisi de bittikten sonra
SELECT * FROM nobet ORDER BY doktor;
text
  doktor  | gorevde
----------+---------
 Dr. Aslı | f
 Dr. Emre | f
(2 rows)

İki oturumun COMMIT komutu da başarıyla döndü ve nöbette kimse kalmadı. İki transaction farklı satırları güncellediği için satır düzeyinde bir çakışma yoktur; REPEATABLE READ bu tür okuma-yazma bağımlılıklarını izlemez. Kılavuz da bu seviyede iş kurallarını korumaya çalışan transaction'ların, dikkatli açık kilit kullanımı olmadan doğru çalışmayacağını belirtir.

SERIALIZABLE: sırayla çalışmış gibi sonuç

SERIALIZABLE, REPEATABLE READ ile aynı şekilde çalışır ve buna ek olarak eşzamanlı serializable transaction'lar arasındaki okuma-yazma bağımlılıklarını izler. Commit edilen transaction'ların sonucu herhangi bir sıralı yürütmeyle açıklanamayacak hâle gelecekse, birini geri alır. Tabloyu sıfırlayıp aynı iki oturumu bu seviyede çalıştırdık:

sql
UPDATE nobet SET gorevde = true;
-- Oturum A ve B: aynı betikler, yalnızca BEGIN ISOLATION LEVEL SERIALIZABLE;
-- (her iki oturumda \set VERBOSITY verbose açık)
text
-- Oturum A
COMMIT

-- Oturum B
ERROR:  40001: could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.
LOCATION:  PreCommit_CheckForSerializationFailure, predicate.c:4727

  doktor  | gorevde
----------+---------
 Dr. Aslı | f
 Dr. Emre | t
(2 rows)

Bu çıktıda yalnızca commit satırlarını ve sondaki kontrol sorgusunu bıraktık; okuma ve güncelleme satırları önceki denemeyle aynıydı. A commit edildi, B'nin commit'i 40001 ile reddedildi ve Dr. Emre nöbette kaldı. HINT satırı da çözümü söylüyor: transaction yeniden denenirse başarılı olabilir. B yeniden denendiğinde nöbette yalnızca bir kişi göreceği için uygulama mantığı onu nöbetten çıkarmayacaktır.

Kılavuz bu izlemenin nasıl yapıldığını da anlatır. PostgreSQL, bir yazmanın daha önceki bir okumayı etkileyip etkilemeyeceğini belirlemek için predicate kilitleri tutar. Bu kilitler pg_locks görünümünde SIReadLock kipiyle görünür ama hiçbir şeyi bekletmez, bu yüzden deadlock'a da yol açmaz. Commit edilen bir transaction'ın kilitlerinin bir süre daha tutulması gerekebilir. SERIALIZABLE READ ONLY DEFERRABLE olarak açılan bir transaction ise başlarken, sorunsuz olduğu garanti edilen bir görüntü elde edene kadar bekleyebilir; sonrasında serializable izleme maliyeti olmadan ve iptal riski taşımadan çalışır. Kılavuz bu kipi uzun raporlar ve yedekler için uygun bulur.

Serialization hatalarını yeniden denemek

Serialization hatalarını yeniden denemek: Mümkün olan transaction'ları READ ONLY olarak tanımlayın., Aktif bağlantı sayısını kontrol altında tutun; gerekirse bağlantı havuzu...

REPEATABLE READ veya SERIALIZABLE kullanan bir uygulama, hata alan transaction'ları yeniden denemeye hazır olmalıdır. PostgreSQL otomatik yeniden deneme yapmaz; kılavuzun gerekçesi, doğru sonucu garanti etmenin ancak SQL'i ve kullanılan değerleri belirleyen uygulama mantığının da baştan çalıştırılmasıyla mümkün olmasıdır. Yani yalnızca son komutu değil, transaction'ı oluşturan bütün akışı tekrarlamalısınız. Hangi kodların yeniden denenebileceği şöyle özetlenebilir:

SQLSTATEAdYeniden deneme
40001serialization_failureKoşulsuz yeniden denenmesi önerilir
40P01deadlock_detectedYeniden denemek genellikle uygundur
23505unique_violationDikkatli olun; kalıcı bir hata da olabilir
23P01exclusion_violationDikkatli olun; kalıcı bir hata da olabilir

Benzersiz anahtar hatalarının neden listede olduğunu kılavuz bir örnekle açıklar: uygulama mevcut anahtarlara bakıp yeni bir değer seçerse ve başka bir oturum aynı anda aynı değeri seçerse 23505 alınır. Bu aslında bir serialization sorunudur ama sunucu, eklenen değerle önceki okuma arasındaki bağı göremediği için onu öyle raporlayamaz. Yeniden denemenin tek seferde başarılı olacağı da garanti değildir; kılavuza göre çok yoğun çakışmada bir transaction'ın tamamlanması birçok deneme alabilir, çakışan hazırlanmış (prepared) bir transaction varsa o bitene kadar ilerleme mümkün olmayabilir. Uygulamanızı birden çok denemeye hazır yazın. Hata kodlarının tamamı için SQLSTATE kodları yazısına bakabilirsiniz.

Kılavuz SERIALIZABLE kullanırken performans için birkaç öneri de verir:

  • Mümkün olan transaction'ları READ ONLY olarak tanımlayın.
  • Aktif bağlantı sayısını kontrol altında tutun; gerekirse bağlantı havuzu kullanın.
  • Bir transaction'a bütünlük için gerekenden fazla iş koymayın ve bağlantıları gereğinden uzun "idle in transaction" durumunda bırakmayın.
  • Serializable koruması sayesinde artık gerekmeyen açık kilitleri, SELECT FOR UPDATE ve SELECT FOR SHARE kullanımlarını kaldırın.
  • Sıralı tarama (sequential scan) her zaman tablo düzeyinde predicate kilidi gerektirir ve serialization hatalarını artırabilir. Kılavuz, indeks taramasını teşvik etmek için random_page_cost değerini düşürmeyi veya cpu_tuple_cost değerini artırmayı önerir; ancak bunun toplam sorgu süresine etkisini de tartmanızı ister.

Hangi seviyeyi seçmeli?

Hangi seviyeyi seçmeli? Tek satırı bilinen bir anahtarla güncelleyen basit işlemler, Birden çok sorgunun aynı anı görmesi gereken raporlar, Birden çok satıra bakan iş kuralları...
İhtiyaçUygun seviyeDikkat edilecek nokta
Tek satırı bilinen bir anahtarla güncelleyen basit işlemlerREAD COMMITTEDKarmaşık WHERE koşullarında yeniden değerlendirme sürprizleri
Birden çok sorgunun aynı anı görmesi gereken raporlarREPEATABLE READGüncelleme yapan transaction'lar 40001 alabilir
Birden çok satıra bakan iş kuralları (nöbet, kontenjan, bakiye toplamı)SERIALIZABLEBütün ilgili transaction'lar bu seviyede olmalı, yeniden deneme şart
Uzun süren salt okunur rapor veya yedekSERIALIZABLE READ ONLY DEFERRABLEBaşlangıçta kısa bir bekleme olabilir

Kılavuzun serializable için vurguladığı bir kazanç da şudur: tek başına doğru çalıştığını gösterebildiğiniz bir transaction, serializable transaction'ların herhangi bir karışımında da ya doğru çalışır ya da commit edilmez. Bu, karmaşık kilit stratejileri tasarlamaktan çoğu zaman daha basittir. Kilitlerle çözüm arıyorsanız kilit kipleri ve SELECT FOR UPDATE yazısı iyi bir başlangıçtır.

Sık yapılan hatalar

  • Seviyeyi ilk sorgudan sonra değiştirmeye çalışmak: SET TRANSACTION ISOLATION LEVEL blokta ilk sorgudan önce verilmelidir, aksi hâlde hata alınır.
  • Yalnızca bazı transaction'ları SERIALIZABLE yapmak: Koruma, birbirini etkileyen transaction'ların hepsi bu seviyede olduğunda geçerlidir.
  • Serializable transaction içinde okunan veriyi commit'ten önce kesin saymak: Kılavuz, okunan verinin transaction başarıyla commit edilene kadar geçerli kabul edilmemesi gerektiğini belirtir; istisna, deferrable salt okunur transaction'lardır.
  • Sadece son komutu tekrarlamak: 40001 alındığında bütün transaction ve onu üreten uygulama mantığı baştan çalıştırılmalıdır.
  • Sequence'ların da izole olduğunu sanmak: Kılavuz, sequence değişikliklerinin bütün transaction'lara hemen göründüğünü ve geri alınmadığını özellikle not eder.

PostgreSQL 18 notu

PostgreSQL 18 sürüm notları izolasyon seviyelerinin tanımını değiştirmiyor. 18.x bakım sürümlerinde ise bu konuya dokunan bir düzeltme var: 18.4 sürüm notlarına göre MERGE, repeatable read veya serializable kipte eşzamanlı güncellenmiş bir satırla karşılaştığında artık serialization hatasını doğru şekilde raporluyor. Bu yazıdaki denemeler 18.1 üzerinde yapıldı; üretimde en güncel 18.x bakım sürümünü kullanmanız önerilir. Diğer yenilikler için PostgreSQL 18 yenilikleri yazısına bakabilirsiniz.

Sık Sorulan Sorular

PostgreSQL'in varsayılan izolasyon seviyesi nedir?

READ COMMITTED. Her komut, kendisi başladığı ana kadar commit edilmiş veriyi görür. SHOW default_transaction_isolation; ile kontrol edebilirsiniz.

READ UNCOMMITTED ile kirli okuma yapabilir miyim?

Hayır. PostgreSQL bu seviyeyi kabul eder ama READ COMMITTED gibi uygular; commit edilmemiş veri hiçbir seviyede başka oturumlara görünmez.

REPEATABLE READ phantom read'e izin verir mi?

PostgreSQL'de hayır. Görüntü transaction başında sabitlendiği için sonradan eklenen satırlar görünmez. Yine de write skew gibi serialization anomalileri mümkündür.

SERIALIZABLE kilit bekletir mi?

Predicate kilitleri hiçbir şeyi bekletmez; çakışmayı commit sırasında ya da güncellemede hata olarak bildirir. Satır güncellemelerinin normal kilitleri ise her seviyede olduğu gibi burada da geçerlidir.

40001 hatasını sunucu otomatik olarak yeniden dener mi?

Hayır. Kılavuz, doğruluğu garanti edemeyeceği için otomatik yeniden deneme sunmadığını açıkça belirtir; yeniden denemeyi uygulamanız yapmalıdır.

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 · Transaction: BEGIN, COMMIT, ROLLBACK · Kilitler ve deadlock · VACUUM ve autovacuum · SQLSTATE hata kodları