Menu

SQLite Transaction: BEGIN, COMMIT, ROLLBACK Kullanımı

SQLite'ta transaction mantığı: BEGIN, COMMIT, ROLLBACK, autocommit modu ve kilitlerin ne zaman alınacağını belirleyen DEFERRED/IMMEDIATE/EXCLUSIVE farkları.

Bu sayfada çalıştırılabilir editörler var — düzenle, çalıştır ve sonucu anında gör.

Transaction Dediğimiz Şey: Ya Hep Ya Hiç

Bir transaction, birden fazla SQL ifadesini tek bir paket gibi ele almanı sağlar; ya hepsi başarılı olur ya da hiçbiri uygulanmaz. Yarı yolda bir şey ters giderse ROLLBACK ile geri alırsınız ve veritabanı tıpkı işlem başlamadan önceki haline döner.

Bunun en bilindik örneği para transferidir:

İki UPDATE birbirine bağlı işlemler. Eğer veritabanı arada çökerse Ada'nın 2000 kuruşu uçar, Boris ise hiçbir şey kazanmamış olur. Bu ikiliyi BEGIN ... COMMIT arasına almak işlemi atomik hale getirir — ya ikisi birden olur ya da hiçbiri.

Autocommit modu: Zaten kullandığınız varsayılan davranış

Şu ana kadar çalıştırdığınız her SQL ifadesi aslında bir transaction'dı. SQLite varsayılan olarak autocommit modunda çalışır: her ifadenin başına ve sonuna otomatik olarak BEGIN ve COMMIT eklenir.

Üç ayrı INSERT, üç ayrı transaction, diske üç ayrı fsync çağrısı. Tek seferlik yazmalar için sorun değil ama toplu yüklemelerde resmen yavaşlatıyor — üstelik bir grup ifadeyi tek parça halinde geri almak da imkânsız hale geliyor. İşte tam burada BEGIN devreye giriyor: bir sonraki COMMIT ya da ROLLBACK'e kadar autocommit modunu kapatıyor.

ROLLBACK: Hiç Olmamış Gibi Davranmak

ROLLBACK, ilgili BEGIN'den sonra yapılan her şeyi siler atar. Veritabanı, transaction öncesi haline geri döner.

UPDATE ve DELETE ifadelerinin ikisi de buharlaşır; tablo BEGIN öncesindeki haline geri döner. İşte tam da bu, çok adımlı bir işlemin ortasında hata patladığında uygulama kodunun temiz bir şekilde geri çekilmesini sağlayan emniyet ağıdır.

Bu arada, transaction içindeki bir constraint ihlali tüm işlemi otomatik olarak geri almaz. Yalnızca hatalı ifadeyi geri alır ve transaction'ı açık bırakır; kararı sana bırakır. "Ya hep ya hiç" mantığı istiyorsanız, hatayı gören uygulamanın kendisi ROLLBACK çalıştırmak zorunda.

Toplu Insert İşlemlerini Hızlandırma

Autocommit modunda her ifade kendi fsync'ini yaptığı için, bir grup işlemi tek bir transaction içine almak çoğu zaman 100 kat hız kazandırır:

COMMIT anında diske tek bir senkronizasyon yapılır, her satır için ayrı ayrı değil. Binlerce satır içe aktarırken işlemin neden kaplumbağa hızında ilerlediğini merak ediyorsanız, cevap genelde tam olarak budur.

DEFERRED, IMMEDIATE, EXCLUSIVE farkı

BEGIN, SQLite'ın kilitleri ne zaman alacağını belirleyen bir mod parametresi kabul eder:

  • BEGIN DEFERRED (varsayılan) — sen okuma ya da yazma yapana kadar hiçbir kilit alınmaz. Yazma kilidi tembel davranır; ilk yazma ifadesinde devreye girer.
  • BEGIN IMMEDIATE — yazma kilidini hemen kapar. Diğer bağlantılar okumaya devam edebilir ama başka hiçbir bağlantı yazma işlemi başlatamaz.
  • BEGIN EXCLUSIVEIMMEDIATE gibidir, ek olarak başka bağlantılar okuma da yapamaz. WAL modunda bu, IMMEDIATE ile aynı şekilde çalışır; fark yalnızca eski rollback journal modunda anlam kazanır.
BEGIN DEFERRED;     -- düz BEGIN ile aynı
BEGIN IMMEDIATE;    -- yazma kilidini şimdi al
BEGIN EXCLUSIVE;    -- her şeyi rezerve et (rollback-journal modu)

Bu seçim, eşzamanlılık (concurrency) açısından kritik. Düz bir BEGIN kullandığınızda iki bağlantı aynı anda transaction başlatabilir, ikisi de keyifle okuma yapabilir, sonra yazma sırası geldiğinde birbirine girerler — write lock'u ikinci isteyen bağlantı SQLITE_BUSY hatası alır ve dahası, bu noktaya kadar yaptığı okumaları çöpe atmak zorunda kalır.

BEGIN IMMEDIATE tam da bu sorunu çözer: yazacağınızı biliyorsanız, write lock'u en başta isteyin. İkinci bağlantı hiç boşa iş yapmadan, en baştan ya bloklanır ya da hızlıca hata döner.

Pratik kural: transaction'ınız yazma yapacaksa BEGIN IMMEDIATE kullanın.

Transaction İçindeki Okumalar Snapshot Görür

Bir transaction açıkken yaptığınız okumalar, veritabanının tutarlı bir anlık görüntüsünü (snapshot) görür: WAL modunda transaction başladığı andaki hali, rollback-journal modunda ise ilk okumayı yaptığınız andaki hali. Başka bağlantılar bu sırada commit yapsa bile, bu değişiklikler sizin sorgularınızda bir anda belirivermez.

Kendi commit'lenmemiş yazdıklarınızı siz görürsünüz; başka bağlantılar göremez. COMMIT yaptığınız anda yeni değer artık herkese görünür hale gelir. İnsanların "SQLite serializable'dır" derken kastettiği şey tam olarak budur — çevirebileceğiniz bir READ COMMITTED ayarı yok, çünkü varsayılan zaten en güçlü izolasyon seviyesi.

Uygulama Kodunda Transaction Kullanımı

Gerçek bir uygulamada bu işin kalıbı genelde şöyledir: gövdeyi try/except (ya da try/catch) bloğuna alırsınız ve hata durumunda ROLLBACK çalıştırırsınız:

-- Herhangi bir istemci kütüphanesi için sözde kod
BEGIN IMMEDIATE;
try:
    UPDATE accounts SET cents = cents - 2000 WHERE owner = 'Ada';
    UPDATE accounts SET cents = cents + 2000 WHERE owner = 'Boris';
    COMMIT;
except:
    ROLLBACK;
    raise;

Çoğu istemci kütüphanesi (Python'un sqlite3'ü, better-sqlite3 vb.) bu işi sizin için bir with bloğu ya da transaction() yardımcısıyla sarmalıyor. Kullandığınız kütüphanenin dokümantasyonuna göz atmakta fayda var — varsayılan davranışlar her zaman beklediğiniz gibi olmuyor. Özellikle Python'un sqlite3 modülü tarihsel olarak tuhaf bir autocommit davranışına sahipti; son sürümlerde bunu düzeltmek için düzgün bir autocommit parametresi eklendi.

İnsanların Sıkça Takıldığı Noktalar

  • DDL komutları transaction içinde çalışır. CREATE TABLE, ALTER TABLE, hatta DROP TABLE bile geri alınabilir. SQLite bu konuda alışılmadık — birçok veritabanı DDL'i otomatik olarak commit eder.
  • VACUUM transaction içinde çalışmaz. Birkaç bakım komutu da öyle. Bunları autocommit modunda çalıştırın.
  • Başarısız bir COMMIT gerçek bir hatadır. COMMIT SQLITE_BUSY döndürürse (nadir ama mümkün), transaction commit edilmemiştir. Kodunuzun bu durumu ele alması gerekir — genelde tekrar deneyerek.
  • Uzun transaction'lar yazıcıları bloklar. Dakikalarca açık kalan bir transaction, diğer yazıcıları da dakikalarca bloklar. Geç açın, hızlıca commit edin.

Sırada: Savepoint'ler

BEGIN ve COMMIT ya hep ya hiç mantığıyla çalışır. Bazen ise transaction'ın sadece bir kısmını geri almak istersiniz — örneğin riskli bir adımdan vazgeçip geri kalanı tutmak gibi. Savepoint'ler tam da bunun için var ve sıradaki konumuz onlar.

Sıkça Sorulan Sorular

SQLite'ta bir transaction nasıl başlatılır?

Önce BEGIN; (ya da BEGIN TRANSACTION;) yazın, işinizi yapın, sonra kaydetmek için COMMIT; ya da geri almak için ROLLBACK; çalıştırın. Açıkça BEGIN demediğiniz sürece her sorgu kendi otomatik commit'lenen transaction'ında çalışır.

BEGIN, BEGIN IMMEDIATE ve BEGIN EXCLUSIVE arasındaki fark nedir?

BEGIN (yani BEGIN DEFERRED) yazma kilidini siz fiilen yazana kadar almaz; bu yüzden başka biri sizden önce davranmışsa ileride SQLITE_BUSY hatasıyla patlayabilir. BEGIN IMMEDIATE yazma kilidini hemen başta alır. BEGIN EXCLUSIVE ise daha da ileri gidip diğer okuyucuları da bloklar (sadece WAL modu dışında anlamlıdır).

SQLite transaction izolasyon seviyelerini destekliyor mu?

SQL standardı anlamında hayır. SQLite pratikte SERIALIZABLE çalışır: her transaction tutarlı bir snapshot görür ve yazmalar sıraya sokulur. READ COMMITTED veya REPEATABLE READ gibi ayar yoktur; sizin verdiğiniz tek karar DEFERRED, IMMEDIATE ya da EXCLUSIVE arasından, bu da ne göreceğinizi değil kilitlerin ne zaman alınacağını belirler.

SQLite iç içe (nested) transaction destekliyor mu?

Doğrudan değil; bir BEGIN içinde tekrar BEGIN çağıramazsınız. İç içe yapı için SAVEPOINT ve RELEASE / ROLLBACK TO kullanılır; bunlar tek bir transaction içinde kısmi geri alma imkânı verir. Detaylar bir sonraki sayfada.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA