SQLite veritabanınız database disk image is malformed, malformed database schema veya file is not a database hatası veriyorsa önce uygulamayı durdurun, veritabanı dosyasını yanındaki -wal ve -journal dosyalarıyla birlikte kopyalayın, kopya üzerinde PRAGMA integrity_check çalıştırın. Sorun yalnızca indekslerdeyse REINDEX yeterlidir; tablo verisi zarar gördüyse .dump ile, o da başarısız olursa .recover ile kurtarabildiğiniz kadar veriyi yeni bir dosyaya aktarırsınız.

SQLite bozulmaya karşı oldukça dayanıklıdır: bir uygulama çökmesi, işletim sistemi çökmesi veya elektrik kesintisi işlemin ortasında olsa bile yarım kalan işlem bir sonraki açılışta otomatik olarak geri alınır. Ancak dayanıklı olmak bağışık olmak demek değildir. Bu yazı, resmî belgelerdeki bozulma nedenlerini, denetim pragmalarının farkını ve kontrollü bir deneme dosyasında bozulmayı nasıl tespit edip giderdiğimizi adım adım gösterir.

Bozulmanın belirtileri

Bozulmanın belirtileri: file is not a database, malformed database schema (t), database disk image is malformed

Bozulma her zaman açılışta fark edilmez. En sık görülen işaretler şunlardır: dosya açılırken veya ilk sorguda SQLITE_CORRUPT (11) koduyla gelen hata mesajları, şema metni okunamadığında görülen malformed database schema, dosyanın başlığı tanınmadığında görülen SQLITE_NOTADB (26) ve hata vermeden yanlış sonuç döndüren sorgular. Sonuncusu en sinsi olanıdır: bozuk bir indeks yüzünden bir WHERE koşulu satırı bulamayabilir ve uygulama bunu "kayıt yok" diye yorumlar.

MesajKodAnlamı
file is not a databaseSQLITE_NOTADB (26)Dosya başlığı SQLite veritabanı olarak tanınmadı
malformed database schema (t)SQLITE_CORRUPT (11)sqlite_schema içindeki tanım ayrıştırılamadı
database disk image is malformedSQLITE_CORRUPT (11)Sayfa veya kayıt yapısında tutarsızlık bulundu

İlk iki mesajı bu yazının denemelerinde kendimiz ürettik; üçüncüsü SQLITE_CORRUPT kodunun C başlık dosyasındaki açıklamasıdır. Mesaj metinleri sürüme göre değişebileceği için uygulamanızda metne değil hata koduna bakın. Kodların tam listesi SQLite hata kodları yazısında.

Veritabanı nasıl bozulur?

Resmî "How To Corrupt Your Database Files" belgesi, bozulma vakalarını ayrıntılı biçimde sınıflandırır. Sunucu işletenler için en önemli başlıkları kendi cümlelerimizle özetleyelim.

Dosyanın başka biri tarafından üzerine yazılması

SQLite dosyası sıradan bir disk dosyasıdır ve herhangi bir süreç onu açıp üzerine yazabilir. Belgede anlatılan gerçek vakalardan biri, kapatılmış bir dosya tanıtıcısının başka bir iş parçacığı tarafından kullanılmaya devam etmesidir: işletim sistemi aynı numarayı veritabanı dosyasına verdiğinde, hata ayıklama çıktısı veritabanının ortasına yazılmıştır. SQLite 3.8.1 sürümünden beri bu nedenle düşük numaralı dosya tanıtıcılarını veritabanı için kullanmaz.

İşlem sürerken yedek almak

Arka planda çalışan bir yedekleme aracı, dosyayı bir yazma işleminin ortasında kopyalarsa yedek eski ve yeni içeriğin karışımı olabilir. Belge güvenli üç yol sayar: sqlite3_rsync aracı, VACUUM INTO komutu ve yedekleme API'si. Dosyayı doğrudan kopyalamak ancak o sırada işlem yoksa güvenlidir ve son yazma başarısız olduysa -journal veya -wal dosyası da ana dosyayla birlikte kopyalanmalıdır. Yöntemlerin ayrıntısı SQLite yedekleme yazısında.

Sıcak günlük dosyasını silmek veya yanlış eşleştirmek

Bir işlem yarıda kaldığında kurtarma için gereken bilgi -journal ya da -wal dosyasındadır; belge bunlara "sıcak" günlük der. Bu dosyalar çökme sonrasında silinir, taşınır veya yeniden adlandırılırsa otomatik kurtarma çalışmaz. Aynı şekilde iki veritabanının günlük dosyalarını takas etmek, veritabanını günlüğü olmadan kopyalamak veya bir dosyanın üzerine başka bir dosya yazıp eski sıcak günlüğü bırakmak da bozulmaya yol açar. "Gereksiz görünen" -wal dosyalarını elle silmek bu yüzden tehlikelidir.

Kilitleme sorunları

SQLite eşzamanlı erişimi dosya kilitleriyle düzenler. Kilitleri hatalı uygulayan dosya sistemleri, özellikle ağ dosya sistemleri, iki sürecin aynı anda yazmasına izin verebilir. Unix'te POSIX danışma kilitlerinin bir tuhaflığı da belgelenmiştir: aynı süreçteki bir iş parçacığı veritabanı dosyasını SQLite dışında açıp close() ile kapattığında, diğer bağlantıların kilitleri sessizce düşer. Uygulamaya iki ayrı SQLite kopyası bağlamak, bağlantılardan birinin farklı kilitleme protokolü kullanması, kullanımdaki dosyayı silip aynı adla yenisini oluşturmak, dosyaya birden fazla sabit bağlantı ile erişmek ve açık bir bağlantıyı fork() ile alt sürece taşımak da aynı başlık altında sayılır. 3.51.0 sürümüyle kilitleri close() tarafından bozulan durumlara karşı ek savunmalar eklenmiştir, ancak belge bunların her derde deva olmadığını belirtir.

Senkronizasyonun başarısız olması

SQLite tutarlılık için zaman zaman işletim sisteminden bekleyen yazmaları kalıcı belleğe aktarmasını ister. Belgeye göre birçok tüketici sınıfı depolama aygıtı bu isteği yerine getirmeden "tamamlandı" der; USB bellekler bu konuda özellikle sorunludur. PRAGMA synchronous=OFF ayarı da bu adımları tamamen atlar ve elektrik kesintisinde bozulmaya açık kapı bırakır. Belge en yüksek güvenilirlik için varsayılan FULL ayarında kalınmasını önerir ve WAL kipinin sıra dışı yazmalara karşı daha toleranslı olduğunu not eder.

Donanım, bellek ve yapılandırma hataları

Disklerde nadiren bit kayması, elektrik kesildiğinde kendi kayıtlarını bozan flaş denetleyicileri ve gerçekte olduğundan büyük kapasite bildiren sahte USB bellekler de listede yer alır. SQLite uygulamanızla aynı adres alanında çalıştığı için uygulamadaki bellek taşmaları da veritabanını bozabilir; bellek eşlemeli G/Ç kullanılıyorsa başıboş bir işaretçi dosyayı anında bozar. Yapılandırma tarafında ise synchronous=OFF, diğer bağlantılar açıkken schema_version değiştirmek, journal_mode=OFF veya MEMORY ile yazarken çökmek ve writable_schema=ON ile şemayı elle düzenlemek sayılır.

SQLite hataları: WAL-reset ve bayat ifade indeksleri

Belge, SQLite'ın kendi hatalarını da açıkça listeler. Bunlardan en günceli "WAL-reset" hatasıdır: WAL kipindeki bir veritabanında, ayrı iş parçacıklarında veya süreçlerde açık iki bağlantı aynı anda yazmaya ya da checkpoint yapmaya çalıştığında nadir bir yarış durumu bozulmaya yol açabilir. Hata 3.7.0 ile 3.51.2 arasındaki sürümlerde bulunur; 3.51.3 ve sonrasında düzeltilmiştir, 3.44.6 ve 3.50.7 için geri taşınmış düzeltmeler vardır. Bu yazının denemelerinde kullandığımız 3.50.6 sürümü etkilenen aralıktadır. Belge olasılığın çok düşük olduğunu ama yükseltmenin önerildiğini belirtir. İkinci başlık, platform veya sürüm değişince değeri hafifçe kayan ifadeler yüzünden oluşan bayat ifade indeksleridir; bu konu SQLite 3.53 yenilikleri yazısında ayrıntılı anlatılıyor.

integrity_check ile quick_check farkı

PRAGMA integrity_check düşük seviyeli bir biçim ve tutarlılık denetimidir. Sırası bozulmuş tablo veya indeks girdilerini, hatalı biçimlenmiş kayıtları, kayıp sayfaları, eksik veya fazla indeks girdilerini, UNIQUE, CHECK ve NOT NULL ihlallerini, boş sayfa listesinin bütünlüğünü ve dosyanın iki kez kullanılan ya da hiç kullanılmayan bölümlerini arar. Sorun yoksa tek satırda ok döner; varsa her sorun ayrı bir satırdır ve varsayılan olarak en fazla 100 hata raporlanır. Parantez içinde bir sayı vererek bu sınırı, bir tablo adı vererek yalnızca o tabloyu ve indekslerini denetleyebilirsiniz. Tablo adıyla kısmi denetim 3.33.0 sürümünde eklenmiştir ve dosyanın tamamını ilgilendiren bazı hataları göremez.

PRAGMA quick_check aynı denetimlerin çoğunu yapar, ancak UNIQUE kısıtlarını ve indeks içeriğinin tablo içeriğiyle eşleşmesini doğrulamaz. Bu yüzden satır sayısıyla doğrusal sürede çalışır; integrity_check ise N log N sürede çalışır. İki pragmanın da yabancı anahtar hatalarını bulmadığını unutmayın: onun için PRAGMA foreign_key_check gerekir. Aşağıdaki örnekte yabancı anahtar denetimi kapalıyken eklenmiş, karşılığı olmayan bir sipariş satırını yalnızca son pragma yakaladı.

sql
PRAGMA foreign_keys = OFF;
CREATE TABLE musteri(id INTEGER PRIMARY KEY, ad TEXT);
CREATE TABLE siparis(id INTEGER PRIMARY KEY, musteri_id INTEGER REFERENCES musteri(id));
INSERT INTO musteri VALUES (1, 'Ayşe');
INSERT INTO siparis VALUES (10, 1), (11, 7);

PRAGMA integrity_check;
-- çıktı: ok
PRAGMA integrity_check(siparis);
-- çıktı: ok
PRAGMA quick_check(5);
-- çıktı: ok
PRAGMA foreign_key_check;
-- çıktı: siparis|11|musteri|0

foreign_key_check çıktısındaki sütunlar sırasıyla tabloyu, ihlal eden satırın rowid değerini, başvurulan tabloyu ve yabancı anahtarın sırasını gösterir. Yabancı anahtarların ayrıntısı için SQLite foreign key yazısına bakın.

DenetimNeyi bulurNeyi bulmazSüre
integrity_checkSayfa, kayıt, indeks eşleşmesi, UNIQUE, CHECK, NOT NULL, boş sayfa listesiYabancı anahtar ihlalleriN log N
quick_checkintegrity_check ile aynı, indeks ve UNIQUE denetimi hariçİndeks-tablo uyumsuzluğu, UNIQUE, yabancı anahtarN
integrity_check(tablo)Yalnızca o tablo ve indeksleriDosya genelindeki kullanılmayan veya çift kullanılan bölümlerTabloya bağlı
foreign_key_checkKarşılığı olmayan yabancı anahtar değerleriSayfa ve indeks bozulmalarıTabloya bağlı

Uygulamalı örnek: bozuk indeksi bulmak ve onarmak

Bozulmayı gerçek bir dosyada görmek için bir deneme yaptık. writable_schema ile bir indeksin tanımını, içindeki veriyle uyuşmayacak biçimde değiştirdik. Bu, güvenlik yazısında anlatılan defensive bayrağının neden önemli olduğunu da gösterir: bayrak açık olsaydı bu değişiklik reddedilirdi. Bu adımları yalnızca silinebilir bir deneme dosyasında uygulayın.

sql
-- bozalt.sql: YALNIZCA deneme dosyasında çalıştırın (sqlite3 lab.db < bozalt.sql)
.dbconfig defensive off
-- çıktı:           defensive off
CREATE TABLE musteri(id INTEGER PRIMARY KEY, ad TEXT, sehir TEXT);
CREATE INDEX musteri_sehir ON musteri(sehir);
INSERT INTO musteri(ad, sehir) VALUES ('Ayşe', 'İzmir'), ('Bora', 'Ankara'), ('Cem', 'Bursa');

-- İndeksin tanımını, içindeki veriyle uyuşmayacak biçimde değiştiriyoruz
PRAGMA writable_schema = ON;
UPDATE sqlite_schema SET sql = 'CREATE INDEX musteri_sehir ON musteri(ad)'
 WHERE name = 'musteri_sehir';
PRAGMA writable_schema = RESET;

Şimdi iki denetimi karşılaştıralım. quick_check indeks içeriğini tabloyla eşleştirmediği için sorunu görmedi; integrity_check ise üç satırın indekste eksik olduğunu raporladı. Daha önemlisi, indeksi kullanan sorgu hiçbir hata vermeden yanlış sonuç döndürdü. REINDEX indeksi tablodaki veriden yeniden kurduktan sonra denetim temiz çıktı ve sorgu doğru sonucu verdi.

sql
PRAGMA quick_check;
-- çıktı: ok
PRAGMA integrity_check;
-- çıktı:
-- row 1 missing from index musteri_sehir
-- row 2 missing from index musteri_sehir
-- row 3 missing from index musteri_sehir

-- Bozuk indeks sorguya yanlış sonuç döndürür
SELECT count(*) FROM musteri WHERE ad = 'Bora';
-- çıktı: 0

REINDEX musteri_sehir;
PRAGMA integrity_check;
-- çıktı: ok
SELECT count(*) FROM musteri WHERE ad = 'Bora';
-- çıktı: 1

Genişletilmiş sonuç kodlarından SQLITE_CORRUPT_INDEX de tam bu durumu anlatır: bir indekste girdinin eksik olduğu tespit edilmiştir ve dosyanın başka bir yerinde sorun yoksa REINDEX sorunu çözebilir. Ayrıca bir deneme daha yaptık: aynı bozuk dosyayı .dump ile metne döküp yeni bir dosyaya yüklediğimizde, döküm indeksleri tablo verisinden yeniden oluşturduğu için yeni dosyanın denetimi ok verdi.

Adım adım kurtarma

Adım adım kurtarma: Yazmayı durdurun, Dosyaları birlikte kopyalayın, Teşhis edin, Metne dökün

Kurtarma sırasında en büyük risk, panikle yapılan bir işlemin elde kalan son tutarlı bilgiyi de yok etmesidir. Aşağıdaki sıra belgelerdeki uyarılara dayanır:

  • Yazmayı durdurun: Uygulamayı veya servisi kapatın ki bozuk dosyaya yeni yazma yapılmasın.
  • Dosyaları birlikte kopyalayın: Ana dosyayla birlikte varsa -wal, -journal ve -shm dosyalarını da kopyalayın. Sıcak günlüğü silmek kurtarmayı imkânsız kılabilir. Bundan sonraki her işlemi kopya üzerinde yapın.
  • Teşhis edin: Kopya üzerinde PRAGMA integrity_check çalıştırın. Hatalar yalnızca indeksleri gösteriyorsa REINDEX deneyin ve denetimi tekrarlayın.
  • Metne dökün: .dump veriyi normal SQL arayüzüyle okur ve ilk bozulma işaretinde durur. İndeks bozulmalarında genellikle yeterlidir.
  • Sayfa düzeyinde kurtarın: .dump takılırsa .recover veriyi doğrudan sayfalardan toplamaya çalışır ve bozulmamış bölümlerin tamamından veri çıkarabilir.
  • Doğrulayın: Yeni dosyada integrity_check ve foreign_key_check çalıştırın, uygulama açısından kritik toplamları ve kayıt sayılarını kontrol edin.
bash
# 1) Önce dosyaları birlikte kopyalayın (uygulama durdurulmuşken)
#    uygulama.db, varsa uygulama.db-wal / uygulama.db-journal / uygulama.db-shm

# 2) Normal okuma yoluyla metne dökmeyi deneyin
sqlite3 kopya.db .dump > dokum.sql

# 3) .dump bozulmaya takılırsa sayfa düzeyinde kurtarma
sqlite3 kopya.db .recover > kurtarilan.sql
sqlite3 kopya.db ".recover --ignore-freelist" > kurtarilan.sql
sqlite3 kopya.db ".recover --lost-and-found yetim_satirlar" > kurtarilan.sql

# 4) Yeni bir dosyaya yükleyin
sqlite3 yeni.db < kurtarilan.sql

Yukarıdaki .recover satırlarını kendi denemelerimizde çalıştıramadık: kullandığımız 3.50.6 komut satırı derlemesinde bu komut bulunmuyor. Komut ve seçenekleri resmî kurtarma belgesinden alınmıştır. Seçenekler tırnak içinde, komutla birlikte verilmelidir. --ignore-freelist boş sayfa listesindeki sayfaları atlar; aksi hâlde silinmiş eski içerik geri gelebilir. --lost-and-found hiçbir tabloya bağlanamayan satırların yazılacağı tablonun adını değiştirir. --no-rowids ise INTEGER PRIMARY KEY olmayan rowid değerlerini çıkarmaz.

Kurtarma, hiçbir tabloyla eşleştirilemeyen satırları lost_and_found adlı bir tabloya yazar. Bu tablonun sütunları satırın bulunduğu ağacın kök sayfası, sayfa numarası, alan sayısı, varsa rowid ve satırdaki değerlerdir. Aynı kök sayfa değerine sahip satırlar çoğu zaman aynı tabloya aittir; bu ipucuyla satırları elle yerine taşıyabilirsiniz. Şemada bu adda bir tablo zaten varsa lost_and_found0, lost_and_found1 gibi adlar kullanılır.

sql
PRAGMA integrity_check;
PRAGMA foreign_key_check;
SELECT count(*) FROM lost_and_found;   -- .recover sahipsiz satır bulduysa bu tablo vardır

Bozulmayı önlemek için kontrol listesi

  • Veritabanını ağ dosya sistemi yerine yerel diskte tutun. Belge, ağ üzerinden dosya paylaşımında kilitleme ve senkronizasyonun güvenilmez olabileceğini ve bunun riskinin kullanıcıya ait olduğunu vurgular.
  • PRAGMA synchronous değerini düşürmeden önce elektrik kesintisinde veri kaybını ve bozulmayı kabul edip etmediğinizi düşünün; kritik verilerde varsayılan değerde kalın.
  • Canlı veritabanını düz dosya kopyalamasıyla yedeklemeyin; yedekleme yazısındaki yöntemleri kullanın ve yedeklerinizi düzenli olarak integrity_check ile sınayın.
  • -wal, -journal ve -shm dosyalarını elle silmeyin, taşımayın; veritabanını taşırken önce tüm bağlantıları kapatın.
  • Veritabanı dosyasını SQLite dışında açıp kapatan araçlardan kaçının; açık bağlantılar varken dosyayı yeniden adlandırmayın veya silmeyin.
  • WAL kipinde birden fazla süreçle yazıyorsanız WAL-reset düzeltmesini içeren bir sürüme geçin. WAL modu ve database is locked yazıları eşzamanlı erişimin ayrıntılarını anlatır.
  • Güvenilmeyen dosyalar için cell_size_check ayarını açın; bozulma daha erken yakalanır.

Sık Sorulan Sorular

Elektrik kesintisi veritabanımı bozar mı?

Varsayılan ayarlarla ve senkronizasyon isteklerini doğru uygulayan bir depolama aygıtında, yarım kalan işlem bir sonraki açılışta otomatik olarak geri alınır. Risk, synchronous=OFF gibi korumaları kapatan ayarlarda ve senkronizasyon konusunda yanlış bilgi veren aygıtlarda ortaya çıkar.

.dump ile .recover arasındaki fark nedir?

.dump veriyi normal SQL arayüzüyle okur ve bozulmanın ilk işaretinde durur. .recover ise veriyi doğrudan veritabanı sayfalarından toplar, bu yüzden bozulmamış bölümlerin tamamından veri çıkarabilir; buna karşılık silinmiş içeriği geri getirebilir ve sonuçlar her zaman şüpheyle karşılanmalıdır.

integrity_check ok verdi, verilerim kesinlikle doğru mu?

Dosya yapısı tutarlıdır, ancak yabancı anahtar ilişkileri bu denetime dahil değildir ve uygulamanın iş kuralları da denetlenmez. foreign_key_check çalıştırın ve kritik toplamları uygulama tarafında doğrulayın.

Bütünlük denetimini ne sıklıkla çalıştırmalıyım?

Belge bir sıklık önermez. Büyük veritabanlarında integrity_check uzun sürebildiği için düzenli bakımda quick_check, yedek doğrulamada ve şüpheli durumlarda integrity_check kullanmak makul bir dengedir. Güvenilmeyen bir dosyayı açarken ise belge denetimi ilk ifade olarak çalıştırmayı önerir.

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 yedekleme · SQLite güvenlik ayarları · SQLite hata kodları · SQLite WAL modu · Veritabanı yedekleme stratejileri