SQLite'ta WAL (write-ahead log, önden yazmalı günlük) kipini açmak için veritabanı dosyasında bir kez PRAGMA journal_mode=WAL; çalıştırmanız yeterlidir; komut başarılı olursa wal döner ve bu ayar dosyada kalıcı olur. WAL kipinde değişiklikler önce ayrı bir -wal dosyasına eklenir, okuyucular ile tek bir yazar birbirini engellemeden çalışabilir ve değişiklikler daha sonra "checkpoint" adı verilen işlemle asıl veritabanı dosyasına aktarılır.

Bu yazıda WAL'ın varsayılan rollback günlüğünden nasıl ayrıldığını, -wal ve -shm dosyalarının ne işe yaradığını, eşzamanlılık farkını iki bağlantılı bir denemeyle, checkpoint türlerini ve WAL dosyasının neden aşırı büyüyebildiğini anlatıyoruz. Sunucu işletenler için dosya izinleri, ağ dosya sistemleri ve yedekleme konusundaki uyarıları da belgelere dayanarak topladık. İşlem kavramlarının kendisi transaction yazısında anlatılıyor.

Rollback günlüğü ile WAL arasındaki fark

Rollback günlüğü ile WAL arasındaki fark: Değişiklik nereye yazılır, Commit anı, Okuyucu ve yazar, Ek dosyalar

Varsayılan DELETE kipinde SQLite değişecek sayfaların orijinal hâlini bir geri alma günlüğüne kopyalar, sonra değişiklikleri doğrudan veritabanı dosyasına yazar; commit, günlük dosyasının silinmesiyle gerçekleşir. Bir çökmede veya ROLLBACK'te orijinal içerik günlükten geri yazılır. WAL bu düzeni tersine çevirir: orijinal içerik veritabanı dosyasında olduğu gibi durur, değişiklikler WAL dosyasının sonuna eklenir ve commit, WAL'a özel bir commit kaydı eklendiği anda gerçekleşir. Böylece commit sırasında asıl dosyaya hiç dokunulmaz ve okuyucular değişmemiş dosyadan okumaya devam edebilir.

Rollback kipinde yalnızca iki temel işlem vardır: okuma ve yazma. WAL'da üçüncüsü eklenir: checkpoint, yani WAL'da biriken işlemlerin asıl dosyaya taşınması. Belgeler WAL'ın avantajlarını şöyle sıralar: çoğu senaryoda belirgin biçimde daha hızlıdır, okuyucular yazarı ve yazar okuyucuları engellemez, disk erişimi daha sıralıdır ve çok daha az fsync çağrısı kullandığı için fsync'in bozuk olduğu sistemlerde daha az risklidir.

ÖzellikRollback (DELETE)WAL
Değişiklik nereye yazılırDoğrudan veritabanı dosyasınaÖnce -wal dosyasına
Commit anıGünlük dosyası silininceWAL'a commit kaydı eklenince
Okuyucu ve yazarDiske yazma sırasında okuyucular dışarıdaAynı anda çalışabilir
Ek dosyalarİşlem sürerken -journalBağlantı açıkken -wal ve -shm
Ağ dosya sistemiRiskli; SQLite bu senaryoda test edilmezÇalışmaz
KalıcılıkVarsayılanAyar dosyada kalır

WAL kipini açmak ve kapatmak

journal_mode pragması yeni kipi metin olarak döndürür. Dönüşüm başarısız olursa, örneğin VFS gerekli paylaşımlı bellek işlevlerini desteklemiyorsa, kip değişmez ve eski kipin adı döner; bu yüzden dönen değeri her zaman kontrol edin. Diğer günlük kiplerinden farklı olarak WAL kalıcıdır: dosya kapatılıp yeniden açıldığında WAL kipinde gelir ve aynı dosyaya bağlanan bütün bağlantılar için geçerli olur. Bu sayede mevcut bir uygulamayı kodunu değiştirmeden WAL'a geçirmek için komut satırında bu pragmayı bir kez çalıştırıp uygulamayı yeniden başlatmanız yeterlidir.

sql
-- sqlite3 wal.db
PRAGMA journal_mode;
-- çıktı: delete
PRAGMA journal_mode = WAL;
-- çıktı: wal

-- bağlantıyı kapatıp aynı dosyayı yeniden açtıktan sonra:
PRAGMA journal_mode;
-- çıktı: wal

Bellek içi veritabanları ise yalnızca MEMORY veya OFF günlük kipinde olabilir; başka bir değere geçme girişimi sessizce yok sayılır. Ayrıca bir işlem açıkken günlük kipi değiştirilemez.

sql
-- sqlite3 :memory:
PRAGMA journal_mode;
-- çıktı: memory
PRAGMA journal_mode = WAL;
-- çıktı: memory

WAL'dan çıkmak için PRAGMA journal_mode=DELETE; kullanılır. Belgelere göre WAL kipindeki bir dosyada başlıktaki 18 ve 19 numaralı baytlarda bulunan dosya biçimi sürüm numaraları 1'den 2'ye çıkarılır; böylece WAL'ı bilmeyen 3.7.0 öncesi sürümler dosyayı açmaya çalışıp durumu kötüleştirmek yerine hata verir. DELETE kipine dönüldüğünde bu numaralar yeniden 1 olur.

journal_modeDavranış
DELETEVarsayılan; günlük her işlem sonunda silinir
TRUNCATEGünlük silinmek yerine sıfır uzunluğa kesilir
PERSISTGünlük kalır, başlığı sıfırlarla ezilir
MEMORYGünlük RAM'de tutulur; işlem ortasında çökme bozulmaya yol açabilir
WALÖnden yazmalı günlük; kalıcı ayar
OFFGünlük yok; atomik commit ve ROLLBACK çalışmaz

-wal ve -shm dosyaları

WAL kipindeki bir veritabanı etkin kullanımdayken diskte üç dosyadan oluşur: veritabanının kendisi, adının sonuna -wal eklenmiş günlük dosyası ve -shm eklenmiş WAL indeksi dosyası. WAL dosyası, işlenmiş ama henüz asıl dosyaya aktarılmamış işlemleri tutan ileri sarma günlüğüdür. -shm dosyası ise aslında dosya olarak kullanılmaz: bağlantılar onu belleğe eşleyip (mmap) eşgüdüm ve WAL içinde sayfaları hızlı bulmak için paylaşımlı bellek olarak kullanır. Veritabanı içeriği barındırmaz ve çökme sonrası kurtarma için gerekmez.

sql
-- sqlite3 wal.db (Windows'ta; Linux'ta .shell ls wal.db* yazın)
PRAGMA journal_mode = WAL;
CREATE TABLE olay(id INTEGER PRIMARY KEY, veri TEXT);
INSERT INTO olay(veri) VALUES ('a');
.shell dir /b wal.db*
-- çıktı:
-- wal.db
-- wal.db-shm
-- wal.db-wal

Son bağlantı düzgün biçimde kapandığında SQLite son bir checkpoint yapar ve -wal ile -shm dosyalarını siler; bu yüzden veritabanı kullanılmıyorken genellikle yalnızca ana dosya görürsünüz. Yukarıdaki oturum bittiğinde dizinde yalnızca wal.db kaldı. Ancak son süreç bağlantıyı kapatmadan sonlanırsa veya son çıkan bağlantı salt okunursa bu temizlik yapılmaz ve dosyalar kalabilir.

Eşzamanlılık farkını görmek

Komut satırı aracının .connection komutuyla aynı dosyaya iki bağlantı açarak farkı gözle görebilirsiniz. Önce varsayılan rollback kipinde, bağlantı 1 bir okuma işlemini açık tutarken bağlantı 0 yazmaya çalışsın:

sql
-- sqlite3 iki.db; varsayılan rollback (DELETE) kipi
CREATE TABLE not_(id INTEGER PRIMARY KEY, metin TEXT);
INSERT INTO not_(metin) VALUES ('ilk');

.connection 1
.open iki.db
BEGIN;
SELECT count(*) FROM not_;          -- bağlantı 1 okuma işlemini açık tutuyor
-- çıktı: 1

.connection 0
INSERT INTO not_(metin) VALUES ('ikinci');
-- hata: database is locked

Rollback kipinde yazar, değişiklikleri dosyaya yazabilmek için bütün okuyucuların çıkmasını beklemek zorundadır; okuyucu işlemini kapatmadığı için yazma meşgul hatası aldı. Aynı denemeyi WAL kipinde tekrarlayalım:

sql
-- aynı deneme, önce PRAGMA journal_mode = WAL; ile
.connection 1
.open iki.db
BEGIN;
SELECT count(*) FROM not_;
-- çıktı: 1

.connection 0
INSERT INTO not_(metin) VALUES ('ikinci');   -- okuyucu varken yazma başarılı
SELECT count(*) FROM not_;
-- çıktı: 2

.connection 1
SELECT count(*) FROM not_;                   -- okuyucu kendi anlık görüntüsünde
-- çıktı: 1
COMMIT;
SELECT count(*) FROM not_;
-- çıktı: 2

Bu kez yazma hemen başarılı oldu. Bağlantı 1 ise işlemini bitirene kadar başladığı andaki görüntüyü görmeye devam etti; belgeler bunu "snapshot isolation" olarak adlandırır. WAL'ın sağladığı eşzamanlılık yalnızca okuyucu ile yazar arasındadır: tek bir WAL dosyası olduğu için aynı anda yine yalnızca bir yazar olabilir. İki yazarın çakışmasıyla ilgili ayrıntılar database is locked yazısında.

Checkpoint: WAL içeriğinin veritabanına aktarılması

Checkpoint: WAL içeriğinin veritabanına aktarılması: PASSIVE (varsayılan), FULL, RESTART, TRUNCATE

Varsayılan olarak SQLite, bir COMMIT WAL dosyasını 1000 sayfa veya daha büyük hâle getirdiğinde ve son bağlantı kapanırken otomatik checkpoint yapar. Bu eşiği PRAGMA wal_autocheckpoint değiştirir; sıfır veya negatif değer otomatik checkpoint'i kapatır. Belgeler bütün otomatik checkpoint'lerin PASSIVE türde olduğunu belirtir. Elle checkpoint için PRAGMA wal_checkpoint kullanılır:

sql
-- sqlite3 wal.db
PRAGMA journal_mode = WAL;
-- çıktı: wal
PRAGMA wal_autocheckpoint;
-- çıktı: 1000

CREATE TABLE olay(id INTEGER PRIMARY KEY, veri TEXT);
WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n WHERE i < 2000)
INSERT INTO olay(veri) SELECT printf('olay-%05d', i) FROM n;

PRAGMA wal_checkpoint(PASSIVE);
-- çıktı: 0|15|15
PRAGMA wal_checkpoint(TRUNCATE);
-- çıktı: 0|0|0

PRAGMA journal_mode = DELETE;
-- çıktı: delete
PRAGMA wal_checkpoint;
-- çıktı: 0|-1|-1

Pragma üç tamsayılı tek bir satır döndürür. İlk sütun genellikle 0'dır; FULL, RESTART veya TRUNCATE türünde bir checkpoint başka bir bağlantı yüzünden tamamlanamazsa 1 olur. İkinci sütun WAL dosyasına yazılmış değişmiş sayfa sayısı, üçüncüsü bunlardan asıl dosyaya başarıyla taşınanların sayısıdır. WAL kipinde değilseniz ikinci ve üçüncü sütun -1 olur. Örnekte PASSIVE checkpoint 15 sayfanın tamamını aktardı; TRUNCATE ise WAL dosyasını sıfır bayta indirdiği için sonraki sayılar sıfır.

TürNe yaparBekler mi
PASSIVE (varsayılan)Kimseyi beklemeden aktarabildiği kadarını aktarırHayır
FULLYazar kalmayana ve okuyucular en son görüntüye geçene kadar bekler, sonra hepsini aktarırYazarları engeller
RESTARTFULL gibi; ayrıca okuyucuların WAL'ı bırakmasını bekler, sonraki yazar WAL'ın başından başlarYazarları engeller
TRUNCATERESTART gibi; başarılı olursa WAL dosyasını sıfır bayta keserYazarları engeller
NOOP (3.51.0+)Hiçbir şey aktarmaz, yalnızca sayıları döndürürHayır
sql
PRAGMA wal_autocheckpoint = 0;      -- otomatik checkpoint kapalı
-- çıktı: 0
PRAGMA wal_autocheckpoint = 1000;   -- varsayılana dön
-- çıktı: 1000

-- SQLite 3.51.0 ve sonrası (deneme ortamımızda çalıştırılamadı):
PRAGMA wal_checkpoint(NOOP);        -- aktarma yapmaz, yalnızca sayıları döndürür

NOOP türü 3.51.0 sürüm notlarında eklenmiş olarak geçer; deneme ortamımızdaki 3.50.6 aracında bulunmadığı için bu satırı çalıştırmadık. Otomatik checkpoint'i kapatan uygulamaların checkpoint'i kendisinin, örneğin boş zamanlarda veya ayrı bir iş parçacığında, çalıştırması gerekir; aksi hâlde WAL dosyası sınırsız büyür.

Okuma ve yazma performansı arasındaki denge

Belgelere göre WAL'da yazma işlemleri çok hızlıdır, çünkü içerik yalnızca bir kez ve sıralı olarak yazılır. Öte yandan okuma performansı WAL dosyası büyüdükçe düşer: her okuyucu aradığı sayfanın WAL'da olup olmadığına bakmak zorundadır. -shm dosyasındaki WAL indeksi bu aramayı hızlandırsa da, iyi okuma performansı için WAL'ı düzenli checkpoint'lerle küçük tutmak gerekir. Checkpoint ise eşitleme (sync) ve daha fazla disk arama gerektirdiği için tek tek yazma işlemlerinden yavaştır.

Varsayılan stratejide çoğu COMMIT çok hızlı biter, ama eşiği aşan COMMIT checkpoint'i de kendi yaptığı için ara sıra belirgin biçimde yavaşlar. Bu dalgalanma sorun oluyorsa otomatik checkpoint kapatılıp ayrı bir iş parçacığında veya süreçte çalıştırılabilir. Okumayı en üst düzeye çıkarmak için sık checkpoint, yazmayı en üst düzeye çıkarmak için seyrek checkpoint gerekir; doğru denge uygulamanıza bağlıdır.

PRAGMA synchronous burada önemli rol oynar. FULL ayarında her commit sonrası WAL dosyası ayrıca eşitlenir; NORMAL ayarında ise çoğu işlemde hiç sync yapılmaz, yalnızca checkpoint sırasında yapılır. Belgelere göre WAL kipinde NORMAL ayarı bozulmaya karşı güvenlidir ve her zaman tutarlıdır, ama dayanıklılıktan (durability) ödün verir: elektrik kesintisi veya sistem çökmesinden sonra son işlenen işlem geri alınmış olabilir. Uygulama çökmeleri ise ayardan bağımsız olarak işlenmiş veriyi kaybettirmez. Belgeler çoğu WAL uygulaması için en iyi dengeyi NORMAL olarak tanımlar; karşılaştırma tablosu performans ayarları yazısında.

WAL dosyası neden aşırı büyür?

Normalde WAL yaklaşık 1000 sayfaya, yani varsayılan sayfa boyutunda kabaca 4 MB'a ulaştığında checkpoint yapılır ve dosya baştan yeniden kullanılır. Checkpoint dosyayı varsayılan olarak küçültmez; üzerine yazmak sona eklemekten hızlı olduğu için mevcut alanı yeniden kullanır. Dosyanın diskte bıraktığı boyutu sınırlamak isterseniz PRAGMA journal_size_limit ile bayt cinsinden bir üst sınır koyabilirsiniz. Belgeler WAL'ın sınırsız büyümesine yol açan üç durumu sayar:

  • Otomatik checkpoint'i kapatmak: Yerine bir şey çalıştırılmazsa büyümeyi durduracak bir mekanizma kalmaz.
  • Checkpoint açlığı: Checkpoint ancak WAL'ı kullanan bir okuyucu kalmadığında tamamlanıp dosyayı sıfırlayabilir. Sürekli en az bir okuyucunun bulunduğu, birbiriyle örtüşen okumalar yapan sistemlerde hiçbir checkpoint tamamlanamaz. Çözüm, okuyucuların olmadığı "boşluklar" bırakmak veya okuyucuları kısa süre bekletme pahasına RESTART ya da TRUNCATE türünde checkpoint çalıştırmaktır.
  • Çok büyük yazma işlemleri: WAL bir yazma işleminin ortasında sıfırlanamaz; büyük bir değişiklik büyük bir WAL dosyası bırakır. Belgeler 3.11.0 sürümünden beri WAL boyutunun işlemin boyutuyla orantılı kaldığını belirtir; daha önceki sürümlerde çok daha büyük olabiliyordu.

Dezavantajlar ve sunucu ortamında dikkat edilecekler

WAL her durum için doğru seçim değildir. Belgelerin saydığı sınırlamaların başında, veritabanını kullanan bütün süreçlerin aynı bilgisayarda olma zorunluluğu gelir: WAL indeksi paylaşımlı bellek gerektirir ve farklı makinelerdeki süreçler bellek paylaşamaz, bu yüzden WAL ağ dosya sistemi üzerinde çalışmaz. Ağ üzerinden kullanım belgesi, verisi uygulamadan ağla ayrılmış durumlar için istemci-sunucu bir veritabanını veya WAL kipindeki dosyaya yalnızca aynı makinedeki süreçlerin eriştiği, uzak istekleri ileten bir aracı (proxy) katmanını önerir. Kısacası veritabanını bir ağ paylaşımında değil, sunucunun yerel diskinde tutun.

Diğer sınırlamalar şunlardır: ATTACH ile bağlı birden çok veritabanını değiştiren işlemler her dosya için ayrı ayrı atomiktir ama küme olarak atomik değildir; WAL kipine girdikten sonra page_size değiştirilemez; çoğunlukla okuyup nadiren yazan uygulamalarda WAL çok az, belgelere göre belki yüzde 1-2 oranında, daha yavaş olabilir; ve -wal ile -shm dosyaları SQLite'ı uygulama dosya biçimi olarak daha az çekici kılabilir. Salt okunur medyaya yazılacak bir veritabanını önce DELETE kipine çevirmek iyi bir uygulamadır.

Dosya izinleri sunucu işletenlerin sık takıldığı noktadır. Belgelere göre 3.22.0 öncesinde WAL kipindeki bir veritabanını okumak için bile yazma izni gerekiyordu. Daha yeni sürümlerde yazma izni olmayan bir WAL veritabanı, -shm ve -wal dosyaları zaten var ve okunabilirse, dizinde bu dosyaları oluşturacak yazma izni varsa veya bağlantı immutable parametresiyle açıldıysa okunabilir. Pratikte web sunucusu kullanıcısının hem veritabanı dosyasına hem de bulunduğu dizine yazabilmesi gerekir; aksi hâlde yan dosyalar oluşturulamaz. Yan dosyalar ana dosyayla aynı dizinde oluştuğu için dizin izinleri de önemlidir.

WAL kipinde de SQLITE_BUSY alınabilir

Okuyucuların yazarı engellememesi büyük ölçüde doğrudur ama belgeler istisnaları da sayar. Başka bir bağlantı veritabanını EXCLUSIVE kilitleme kipinde açmışsa bütün sorgular meşgul hatası alır; belgeler Chrome ve Firefox'un veritabanlarını bu kipte açtığını örnek verir. Son bağlantı kapanırken WAL ve paylaşımlı bellek dosyalarını temizlemek için kısa bir süre özel kilit tutar; o sırada açılmaya çalışan bağlantı meşgul hatası alabilir. Son bağlantı çöktüyse, ilk açılan yeni bağlantı kurtarma işlemi yaparken özel kilit tutar ve araya giren üçüncü bir bağlantı yine meşgul hatası alır. Uygulamanız bu durumlara hazır olmalı; PRAGMA busy_timeout bunların çoğunu kısa bir beklemeyle atlatır.

Paylaşımlı bellek kullanılamayan ortamlar için de bir yol vardır: ilk erişimden önce PRAGMA locking_mode=EXCLUSIVE ayarlanırsa SQLite -shm dosyası oluşturmaz, WAL indeksini yığın bellekte tutar. Bu durumda veritabanına yalnızca o süreç erişebilir ve WAL kipinden çıkılmadan kilitleme kipi NORMAL'e döndürülemez.

WAL-reset hatası ve sürüm seçimi

Belgeler 2026 Mart'ında bulunan ve "WAL-reset bug" adı verilen nadir bir hatayı ayrıca açıklar. Hatanın 3.7.0 ile 3.51.2 arasındaki sürümlerde bulunması muhtemeldir; 3.51.3 ve sonrasında düzeltilmiştir ve bazı eski sürüm dalları için 3.44.6 ve 3.50.7 düzeltme sürümleri yayımlanmıştır. Hata yalnızca WAL kipinde, aynı dosyada farklı iş parçacıklarında veya süreçlerde iki ya da daha fazla bağlantı açıkken ve bu bağlantılar aynı anda yazmaya veya checkpoint yapmaya çalıştığında, çok dar bir zamanlama penceresinde ortaya çıkabilir ve veritabanını bozabilir.

Belgeler bunun bir acil durum olmadığını, gerçek kullanımda görülme olasılığının çok düşük olduğunu, ancak sonuçları ciddi olduğu için düzeltilmiş bir sürüme geçilmesini önerir. Bu yazının örneklerini çalıştırdığımız 3.50.6 aracı etkilenen aralıktadır; çok süreçli bir sunucu uygulamasında WAL kullanıyorsanız kütüphanenizin sürümünü SELECT sqlite_version(); ile kontrol edin.

Sık yapılan hatalar

Sık yapılan hatalar: Yalnızca ana dosyayı kopyalamak, -wal veya -shm dosyasını "çöp" sanıp silmek, Veritabanını ağ paylaşımına koymak, Dönen değeri kontrol etmemek
  • Yalnızca ana dosyayı kopyalamak: İşlenmiş ama checkpoint yapılmamış veriler -wal dosyasındadır; canlı bir veritabanını cp ile kopyalamak yerine yedekleme API'si veya VACUUM INTO kullanın.
  • -wal veya -shm dosyasını "çöp" sanıp silmek: İşlenmiş işlemler kaybolabilir, dosya bozulabilir.
  • Veritabanını ağ paylaşımına koymak: WAL ağ dosya sistemlerinde çalışmaz.
  • Dönen değeri kontrol etmemek: journal_mode=WAL başarısız olursa eski kipin adı döner.
  • Uzun okuma işlemlerini açık bırakmak: Checkpoint ilerleyemez ve WAL büyür.

Sık Sorulan Sorular

WAL kipini her bağlantıda yeniden açmam gerekir mi?

Hayır. WAL kalıcıdır; bir kez ayarlandıktan sonra dosya her açılışta WAL kipinde gelir. Bağlantı açılışında yeniden çalıştırmak zararsızdır ve kipin gerçekten WAL olduğunu doğrulamanın kolay bir yoludur.

-wal dosyası neden kapanıştan sonra da duruyor?

Son bağlantı düzgün kapanmadan sonlanmış veya son çıkan bağlantı salt okunur olabilir. Veritabanını yazma izniyle bir kez açıp kapatmak checkpoint yapıp dosyaları temizler.

WAL birden çok yazara izin verir mi?

Hayır. Okuyucular ve tek bir yazar aynı anda çalışabilir; ikinci yazar sırasını bekler.

Hangi işlemler için rollback kipi daha uygun?

Belgeler çok büyük işlemler için rollback kiplerinin daha uygun olabileceğini, ağ dosya sistemi kullanımında ve salt okunur medyada ise WAL'dan kaçınılması gerektiğini belirtir.

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: SQLite öğrenme rehberi · SQLite transaction · SQLite database is locked hatası · SQLite yedekleme · SQLite performans ayarları · SQLite PRAGMA komutları