PostgreSQL'de transaction, birden fazla SQL komutunu tek ve bölünmez bir iş olarak çalıştırmanızı sağlar: BEGIN ile başlatırsınız, COMMIT ile tüm değişiklikleri birlikte kalıcı yaparsınız, ROLLBACK ile de hepsini birden iptal edersiniz. Arada bir hata olursa ya da vazgeçerseniz veritabanı, transaction hiç başlamamış gibi eski hâline döner. Siz BEGIN yazmasanız bile PostgreSQL her komutu kendi küçük transaction'ı içinde çalıştırır; buna autocommit davranışı denir.
Bu yazıda transaction bloğunun temel komutlarını, kısmi geri alma için SAVEPOINT kullanımını, hata sonrasında oluşan "aborted" durumunu, psql'in ON_ERROR_ROLLBACK ve AUTOCOMMIT değişkenlerini ve uzun süre açık kalan transaction'lara karşı zaman aşımı ayarlarını çalışan örneklerle anlatıyoruz. Aynı anda çalışan transaction'ların birbirini nasıl gördüğü izolasyon seviyeleri yazısının, bekleme ve kilitlenme konuları da kilitler ve deadlock yazısının konusudur.
Transaction neyi garanti eder?

Kılavuzdaki klasik örnek bir banka havalesidir: bir hesaptan para düşülür, diğerine eklenir. Bu iki UPDATE ayrı ayrı çalışırsa ve arada sunucu çökerse, para bir hesaptan çıkmış ama diğerine girmemiş olabilir. Transaction bu riski üç açıdan ortadan kaldırır:
- Atomiklik: Bloğun içindeki adımların ya hepsi uygulanır ya hiçbiri. Yarım kalmış bir ara durum veritabanına yazılmaz.
- Kalıcılık: Sunucu
COMMITkomutunu onayladığında değişiklikler kalıcı depolamaya kaydedilmiştir; hemen ardından gelen bir çökme bile onları kaybettirmez. - Görünürlük: Açık bir transaction'ın yaptığı değişiklikleri diğer oturumlar göremez. Commit anında değişikliklerin tamamı aynı anda görünür hâle gelir.
Bu davranışın altında PostgreSQL'in çok sürümlü eşzamanlılık denetimi (MVCC) yatar. Genel tanıtım için PostgreSQL nedir yazısına, satır sürümlerinin nasıl tutulduğuna ve eşzamanlı okumalara ise izolasyon ve MVCC yazısına bakabilirsiniz.
BEGIN, COMMIT ve ROLLBACK ile ilk örnek
Aşağıdaki örnekte iki hesaplı küçük bir tablo kuruyoruz. CHECK (bakiye >= 0) kısıtı, ileride hata senaryosunu göstermek için işimize yarayacak. İlk blokta havale COMMIT ile kalıcı oluyor, ikinci blokta ise ROLLBACK ile iptal ediliyor:
CREATE TABLE hesap (
id int PRIMARY KEY,
sahip text NOT NULL,
bakiye numeric(12,2) NOT NULL CHECK (bakiye >= 0)
);
INSERT INTO hesap VALUES (1, 'Ayşe', 500), (2, 'Mehmet', 100);
BEGIN;
UPDATE hesap SET bakiye = bakiye - 200 WHERE id = 1;
UPDATE hesap SET bakiye = bakiye + 200 WHERE id = 2;
COMMIT;
SELECT * FROM hesap ORDER BY id; id | sahip | bakiye
----+--------+--------
1 | Ayşe | 300.00
2 | Mehmet | 300.00
(2 rows)BEGIN;
UPDATE hesap SET bakiye = bakiye - 50 WHERE id = 1;
SELECT bakiye FROM hesap WHERE id = 1; -- sonuç: 250.00 (yalnızca bu oturum görür)
ROLLBACK;
SELECT bakiye FROM hesap WHERE id = 1; -- sonuç: 300.00İkinci blokta dikkat edilmesi gereken nokta şudur: transaction açıkken yapılan SELECT, kendi değişikliğini (250.00) görür. Aynı anda başka bir oturum bu satırı okusaydı hâlâ 300.00 görürdü. ROLLBACK sonrasında değişiklik hiç yapılmamış gibi ortadan kalkar.
Komutların eş anlamlıları da vardır. Aşağıdaki tablo, kılavuzun başvuru sayfalarındaki karşılıkları özetler:
| Komut | Ne yapar | Not |
|---|---|---|
| BEGIN [WORK | TRANSACTION] | Transaction bloğu başlatır | WORK ve TRANSACTION sözcüklerinin etkisi yoktur |
| START TRANSACTION | BEGIN ile aynı işi yapar | SQL standardındaki yazım |
| COMMIT / END | Değişiklikleri kalıcı yapar | END bir PostgreSQL eklentisidir |
| ROLLBACK / ABORT | Tüm değişiklikleri iptal eder | ABORT tarihsel nedenlerle vardır |
| SAVEPOINT ad | Blok içinde geri dönülebilecek bir nokta koyar | Yalnızca blok içinde kullanılabilir |
| ROLLBACK TO SAVEPOINT ad | O noktadan sonraki işleri geri alır | Savepoint tanımlı kalmaya devam eder |
| RELEASE SAVEPOINT ad | Savepoint'i kaldırır, işler korunur | Aborted durumda kullanılamaz |
Autocommit: BEGIN yazmadığınızda ne olur?
PostgreSQL her SQL komutunu bir transaction içinde çalıştırır. Siz BEGIN vermezseniz her komutun etrafına görünmez bir BEGIN ve, komut başarılıysa, bir COMMIT eklenir; başarısızsa o tek komut geri alınır. Bu yüzden tek bir UPDATE bile ya tamamen uygulanır ya hiç uygulanmaz.
Kılavuz burada önemli bir performans notu da düşer: transaction başlatmak ve commit etmek kayda değer CPU ve disk işi gerektirir. Bu nedenle çok sayıda küçük komutu tek bir blok içinde çalıştırmak, her birini ayrı ayrı commit etmekten daha hızlıdır. Toplu yükleme senaryolarında bu konu COPY ile toplu veri aktarımı yazısında ayrıca ele alınıyor.
Hata sonrası "aborted" transaction
Yeni başlayanların en sık takıldığı durum budur. Transaction bloğu içindeki bir komut hata verdiğinde PostgreSQL bloğu iptal edilmiş (aborted) duruma geçirir. Bu noktadan sonra gönderdiğiniz her komut reddedilir; blok ancak ROLLBACK ile (ya da bir savepoint'e dönerek) kapatılabilir. Bakiyeyi eksiye düşürmeye çalışan şu örneğe bakın:
BEGIN;
UPDATE hesap SET bakiye = bakiye - 1000 WHERE id = 1;
SELECT * FROM hesap;
COMMIT;BEGIN
ERROR: new row for relation "hesap" violates check constraint "hesap_bakiye_check"
DETAIL: Failing row contains (1, Ayşe, -700.00).
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACKÇıktının son satırına dikkat edin: biz COMMIT yazdık ama sunucu ROLLBACK yanıtı verdi. Aborted durumdaki bir blokta COMMIT aslında geri alma işlemi yapar; hata öncesindeki başarılı komutlar dahil hiçbir değişiklik kalıcı olmaz. Uygulama kodunda "COMMIT çalıştı, demek ki kayıt yazıldı" varsayımı bu yüzden tehlikelidir; commit'in sonucunu ve önceki komutların hata durumunu mutlaka kontrol edin. Hata kodlarının anlamı için sık hatalar ve SQLSTATE kodları yazısına bakabilirsiniz.
SAVEPOINT, ROLLBACK TO ve RELEASE

Savepoint, transaction içine konan bir işarettir. ROLLBACK TO SAVEPOINT komutu, işaretten sonra yapılan her şeyi geri alır ama işaretten önceki değişiklikleri korur. Kılavuzun da vurguladığı gibi, hata yüzünden aborted duruma düşmüş bir bloğu tamamen geri almadan kurtarmanın tek yolu budur:
BEGIN;
INSERT INTO hesap VALUES (3, 'Zeynep', 50);
SAVEPOINT once;
INSERT INTO hesap VALUES (3, 'Zeynep', 75); -- aynı id: hata
ROLLBACK TO SAVEPOINT once;
INSERT INTO hesap VALUES (4, 'Can', 75);
RELEASE SAVEPOINT once;
COMMIT;
SELECT * FROM hesap ORDER BY id;BEGIN
INSERT 0 1
SAVEPOINT
ERROR: duplicate key value violates unique constraint "hesap_pkey"
DETAIL: Key (id)=(3) already exists.
ROLLBACK
INSERT 0 1
RELEASE
COMMIT
id | sahip | bakiye
----+--------+--------
1 | Ayşe | 300.00
2 | Mehmet | 300.00
3 | Zeynep | 50.00
4 | Can | 75.00
(4 rows)Hata, yalnızca savepoint'ten sonraki komutu geçersiz kıldı; Zeynep'in ilk kaydı ve sonradan eklenen Can kaydı birlikte commit edildi. Savepoint'lerle ilgili kılavuzda geçen birkaç kural günlük kullanımda önemlidir:
ROLLBACK TOsonrasında savepoint tanımlı kalır; aynı noktaya birden çok kez dönebilirsiniz.- Bir savepoint'e dönmek ya da onu serbest bırakmak, ondan sonra tanımlanmış bütün savepoint'leri de otomatik olarak kaldırır.
- Aynı adla ikinci bir savepoint tanımlarsanız eskisi silinmez, yalnızca gölgede kalır. Yenisini
RELEASEettiğinizde eski savepoint yeniden erişilebilir olur. SQL standardı burada eskisinin silinmesini ister; PostgreSQL farklı davranır. - Aborted durumdaki bir blokta
RELEASE SAVEPOINTçalışmaz; önceROLLBACK TOgerekir. - Savepoint'ten sonra alınan kilitler, o savepoint'e geri dönüldüğünde hemen bırakılır.
- İmleçler (cursor) savepoint'lere karşı tam olarak transaction'a bağlı davranmaz:
FETCHile ilerleyen imlecin konumu geri alınmaz.
PL/pgSQL içindeki BEGIN ... EXCEPTION blokları da arka planda aynı alt transaction mantığıyla çalışır; ayrıntıları PL/pgSQL hata yakalama yazısında bulabilirsiniz.
psql ile transaction: ON_ERROR_ROLLBACK, AUTOCOMMIT ve tek transaction
psql istemcisinin üç özelliği transaction davranışını doğrudan değiştirir. Birincisi ON_ERROR_ROLLBACK değişkenidir. on yapıldığında psql, blok içindeki her komuttan önce sizin yerinize gizli bir savepoint koyar ve komut hata verirse o savepoint'e döner. Böylece hata transaction'ı bozmaz:
\set ON_ERROR_ROLLBACK on
BEGIN;
INSERT INTO hesap VALUES (5, 'Elif', 20);
INSERT INTO hesap VALUES (5, 'Elif', 20); -- hata, ama blok devam eder
INSERT INTO hesap VALUES (6, 'Burak', 30);
COMMIT;
SELECT id, sahip FROM hesap WHERE id >= 5 ORDER BY id;BEGIN
INSERT 0 1
ERROR: duplicate key value violates unique constraint "hesap_pkey"
DETAIL: Key (id)=(5) already exists.
INSERT 0 1
COMMIT
id | sahip
----+-------
5 | Elif
6 | Burak
(2 rows)Değişken interactive değerini de alır; bu durumda hata yutma yalnızca etkileşimli oturumda yapılır, -f ile okunan betiklerde yapılmaz. Betiklerde bu ayrım güvenlidir, çünkü bir dağıtım betiğinin hatayı görmezden gelip devam etmesi çoğu zaman istenmez.
İkincisi AUTOCOMMIT değişkenidir. Varsayılan on değerinde her komut başarıyla bitince commit edilir. off yapıldığında psql, transaction dışında gönderilen ilk uygun komuttan önce kendisi BEGIN gönderir; siz COMMIT demedikçe hiçbir şey kalıcı olmaz:
\set AUTOCOMMIT off
INSERT INTO hesap VALUES (9, 'Kaan', 10);
ROLLBACK;
SELECT count(*) FROM hesap WHERE id = 9; -- sonuç: 0Üçüncüsü komut satırındaki -1 (--single-transaction) seçeneğidir. Bu seçenek, -f veya -c ile verilen her şeyi tek bir BEGIN ... COMMIT içine alır. ON_ERROR_STOP de açıksa hata anında COMMIT yerine ROLLBACK gönderilir ve psql 3 çıkış koduyla biter. Üç satırlık bir betikte ikinci satır hata verdiğinde birinci satırın da geri alındığını görebilirsiniz:
psql -h 127.0.0.1 -U postgres -d ornek_tx -1 -v ON_ERROR_STOP=1 -f yukle.sql
echo $?INSERT 0 1
psql:yukle.sql:2: ERROR: duplicate key value violates unique constraint "hesap_pkey"
DETAIL: Key (id)=(1) already exists.
3Betiğin ilk satırı INSERT 0 1 yanıtı aldı, ama transaction geri alındığı için o kayıt tabloda yok. psql'in diğer seçenekleri için psql komutları rehberine bakabilirsiniz.
Transaction özellikleri: READ ONLY, izolasyon ve AND CHAIN
BEGIN komutu isteğe bağlı olarak transaction kipleri alır: izolasyon seviyesi, READ WRITE ya da READ ONLY erişim kipi ve DEFERRABLE. Aynı ayarlar blok başladıktan sonra SET TRANSACTION ile de verilebilir; ancak izolasyon seviyesi blokta ilk sorgu veya veri değiştiren komut çalıştıktan sonra değiştirilemez:
BEGIN READ ONLY;
DELETE FROM hesap WHERE id = 6;
ROLLBACK;
BEGIN;
SELECT 1;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
ROLLBACK;BEGIN
ERROR: cannot execute DELETE in a read-only transaction
ROLLBACK
BEGIN
?column?
----------
1
(1 row)
ERROR: SET TRANSACTION ISOLATION LEVEL must be called before any query
ROLLBACKSalt okunur kip; geçici olmayan tablolara INSERT, UPDATE, DELETE, MERGE ve COPY FROM, bütün CREATE, ALTER, DROP komutları, GRANT, REVOKE ve TRUNCATE gibi işlemleri engeller. Kılavuz bunun üst düzey bir kavram olduğunu, diske yapılan her yazmayı engellemediğini de not eder. Raporlama oturumlarında yanlışlıkla veri değiştirmeye karşı kullanışlı bir emniyet kemeridir.
COMMIT AND CHAIN ve ROLLBACK AND CHAIN, mevcut transaction'ı bitirip aynı özelliklere sahip yeni bir transaction'ı hemen başlatır. Aşağıda zincirlenen yeni bloğun da repeatable read seviyesinde başladığını görüyoruz:
BEGIN ISOLATION LEVEL REPEATABLE READ;
UPDATE hesap SET bakiye = bakiye + 1 WHERE id = 6;
COMMIT AND CHAIN;
SHOW transaction_isolation; -- sonuç: repeatable read
COMMIT;İzolasyon seviyelerinin anlamı ve hangisini ne zaman seçeceğiniz izolasyon seviyeleri yazısında ayrıntılı olarak anlatılıyor.
Transaction içinde neler geri alınır, neler alınmaz?
PostgreSQL'de tablo oluşturma gibi şema değişiklikleri de transaction'a dahildir. Aşağıdaki örnekte oluşturulan tablo, ROLLBACK ile birlikte ortadan kalkıyor; to_regclass boş (NULL) dönüyor:
BEGIN;
CREATE TABLE gecici_rapor (x int);
INSERT INTO gecici_rapor VALUES (1);
ROLLBACK;
SELECT to_regclass('gecici_rapor'); -- sonuç: NULL (boş)Bu özellik şema geçişlerinde (migration) çok işe yarar: bir dizi ALTER TABLE komutunu tek blokta çalıştırıp, biri başarısız olursa hepsini geri alabilirsiniz. Buna karşılık bazı nesneler transaction kuralına uymaz. Kılavuz özellikle sequence'ları belirtir: nextval ile alınan bir değer, transaction geri alınsa bile geri verilmez ve diğer oturumlara hemen görünür. Identity sütunlarında bu durum numara boşluğu olarak karşınıza çıkar:
CREATE TABLE siparis (id int GENERATED ALWAYS AS IDENTITY, urun text);
BEGIN;
INSERT INTO siparis (urun) VALUES ('kalem');
ROLLBACK;
INSERT INTO siparis (urun) VALUES ('defter');
SELECT * FROM siparis; id | urun
----+--------
2 | defter
(1 row)Geri alınan "kalem" satırı 1 numarasını tüketti, bu yüzden "defter" 2 aldı. Kesintisiz numara gerektiren fatura gibi alanlar için sequence'a güvenmemek gerekir; ayrıntılar identity, serial ve sequence yazısında. Bazı komutlar ise transaction bloğu içinde hiç çalışmaz. Örneğin VACUUM blok içinde reddedilir, LOCK TABLE ise tam tersine yalnızca blok içinde kullanılabilir:
BEGIN;
VACUUM hesap;
ROLLBACK;
LOCK TABLE hesap IN SHARE MODE;BEGIN
ERROR: VACUUM cannot run inside a transaction block
ROLLBACK
ERROR: LOCK TABLE can only be used in transaction blocksİki aşamalı commit için PREPARE TRANSACTION, COMMIT PREPARED ve ROLLBACK PREPARED komutları da vardır. Hazırlanan bir transaction oturumdan ayrılır ve durumu diske yazılır; daha sonra herhangi bir oturumdan commit veya geri alma yapılabilir. Bu, dağıtık işlem yöneticileri için tasarlanmış ileri bir özelliktir ve unutulan hazırlanmış transaction'lar vacuum işlemini engelleyebilir.
Uzun ve boşta bekleyen transaction'lar

Açık bir transaction iki şekilde zarar verir. Birincisi aldığı kilitleri sonuna kadar tutar; aynı satırı güncellemek isteyen diğer oturumlar bekler. İkincisi, kılavuzun da belirttiği gibi, açık bir transaction yalnızca kendisinin görebileceği eski satır sürümlerinin temizlenmesini engeller ve tablo şişmesine katkıda bulunur. Bu etkiyi VACUUM ve bloat yazısında gerçek çıktılarla gösteriyoruz.
En tehlikelisi "idle in transaction" durumudur: uygulama BEGIN göndermiş, birkaç komut çalıştırmış ve sonra kullanıcıdan yanıt beklerken bağlantıyı açık bırakmıştır. Buna karşı idle_in_transaction_session_timeout parametresi kullanılır. Aşağıdaki denemede süreyi bir saniyeye indirip blok açıkken iki saniye bekledik. Bekleme için psql'in kabuk komutu çalıştıran \! meta komutuyla Windows'taki ping aracını kullandık; o sırada sunucuya hiçbir sorgu gitmiyor:
SET idle_in_transaction_session_timeout = '1s';
BEGIN;
UPDATE hesap SET bakiye = bakiye WHERE id = 1;
\! ping -n 3 127.0.0.1 > NUL
SELECT 1;SET
BEGIN
UPDATE 1
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
error: connection to server was lostSunucu günlüğünde nedeni açıkça yazıyordu: FATAL: terminating connection due to idle-in-transaction timeout. Bağlantı kesildiği için açık transaction geri alındı ve tuttuğu kilitler bırakıldı. Benzer amaçlı diğer parametreler şunlardır:
| Parametre | Neyi sınırlar | Aşıldığında |
|---|---|---|
| statement_timeout | Tek bir komutun süresini | Komut iptal edilir |
| lock_timeout | Bir kilidi bekleme süresini | Bekleyen komut iptal edilir |
| idle_in_transaction_session_timeout | Açık transaction içinde boşta bekleme süresini | Oturum sonlandırılır |
| transaction_timeout | Bir transaction'ın toplam süresini | Oturum sonlandırılır |
| idle_session_timeout | Transaction dışında boşta bekleme süresini | Oturum sonlandırılır |
Hepsinin varsayılanı 0, yani kapalıdır; birim yazmazsanız değer milisaniye kabul edilir. Kılavuz, statement_timeout, lock_timeout ve transaction_timeout değerlerini postgresql.conf içinde genel olarak ayarlamayı önermez, çünkü bütün oturumları etkiler. Bunun yerine SET, ALTER ROLE ... SET veya ALTER DATABASE ... SET ile ilgili kullanıcıya ya da veritabanına özel ayar yapmak daha kontrollüdür. Parametre bağlamları için postgresql.conf ve ALTER SYSTEM yazısına, hangi oturumun ne kadar süredir açık olduğunu bulmak için pg_stat_activity yazısına bakabilirsiniz.
Sık yapılan hatalar
- Hata sonrası COMMIT'e güvenmek: Aborted bir blokta
COMMITaslındaROLLBACKyapar. Uygulama her komutun sonucunu kontrol etmelidir. - Transaction açıkken kullanıcı beklemek: Form doldurulurken açık tutulan bir blok, kilitleri ve eski satır sürümlerini dakikalarca tutar. Kullanıcı etkileşimini transaction dışında yapın.
- İç içe BEGIN beklemek: Blok içindeyken ikinci
BEGINyalnızcaWARNING: there is already a transaction in progressuyarısı verir, yeni bir seviye açmaz. İç içe geri alma için savepoint kullanın. - Blok dışında COMMIT ya da ROLLBACK yazmak: Zarar vermez ama
WARNING: there is no transaction in progressuyarısı üretir; genellikle bloğun zaten bir hata yüzünden kapandığının işaretidir. - Sequence değerinin geri geleceğini sanmak: Geri alınan transaction'lar numara boşluğu bırakır.
- Çok büyük tek transaction: Milyonlarca satırı tek blokta güncellemek uzun süre kilit tutar ve vacuum'u geciktirir. Mantıklı parçalara bölmeyi düşünün.
PostgreSQL 18 notu
PostgreSQL 18 sürüm notları bu yazıdaki komutların söz dizimini değiştirmiyor. Commit zamanını ilgilendiren bir uyumluluk değişikliği var: AFTER tetikleyicileri artık, olayın kuyruğa alındığı anda etkin olan rolle çalışıyor. Önceden tetikleyici çalıştığı andaki rol (örneğin COMMIT sırasında etkin olan rol) kullanılıyordu. Transaction ortasında SET ROLE ile rol değiştiren uygulamalar bu farkı dikkate almalıdır; tetikleyiciler için trigger oluşturma yazısına bakabilirsiniz. Bu sürümün diğer yenilikleri PostgreSQL 18 yenilikleri yazısında.
Sık Sorulan Sorular
PostgreSQL'de iç içe transaction var mı?
Gerçek anlamda yok. Blok içindeyken verilen ikinci BEGIN yalnızca uyarı üretir. İç içe yapıyı savepoint'lerle kurarsınız: her SAVEPOINT bir alt seviye gibi davranır, ROLLBACK TO ile o seviyeyi geri alır, RELEASE ile üst seviyeye katarsınız.
ROLLBACK ile ABORT arasında fark var mı?
Davranış olarak yok. ABORT tarihsel nedenlerle duran bir eş anlamlıdır; standart SQL yazımı ROLLBACK'tir. Aynı şekilde END de COMMIT ile aynı işi yapan bir PostgreSQL eklentisidir.
Bir hata tüm transaction'ı mı bozar?
Evet; blok içindeki tek bir hata bloğu aborted duruma geçirir ve sonraki komutlar reddedilir. Bloğu kurtarmak için hatadan önce bir savepoint koymuş olmanız gerekir. psql'de ON_ERROR_ROLLBACK bunu sizin yerinize her komut için yapar.
CREATE TABLE ya da ALTER TABLE geri alınabilir mi?
Evet. PostgreSQL'de bu tür şema komutları da transaction'a dahildir ve ROLLBACK ile geri alınır. İstisnalar vardır: VACUUM gibi bazı komutlar blok içinde hiç çalışmaz; komutun başvuru sayfası bu kısıtı ayrıca belirtir.
Hangi zaman aşımı parametresini kullanmalıyım?
Unutulan açık transaction'lar sorun çıkarıyorsa idle_in_transaction_session_timeout, kilit beklemeleri uygulamayı donduruyorsa lock_timeout, kontrolden çıkan uzun sorgular için statement_timeout uygundur. Değerleri iş yükünüze göre seçin ve genel yapılandırma yerine rol veya veritabanı düzeyinde ayarlamayı tercih edin.
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 · İzolasyon seviyeleri ve MVCC · Kilitler ve deadlock · PL/pgSQL hata yakalama · VACUUM ve bloat