Hatalar SQLite'ın Sana Bir Şey Anlatma Biçimidir
SQLite'ın hata mesajları kısa ve bazen kafa karıştırıcı görünebilir, ama aslında hepsi belirli bir grup temel soruna işaret eder. Üretim ortamında karşılaşacağın sqlite hatalarının büyük çoğunluğu beş kategoriye sığar: kilitlenme, izin sorunları, veritabanı bozulması, şema uyumsuzlukları ve kısıt ihlalleri. Bu sayfada her birini tek tek ele alacağız — neyin tetiklediğini, gerçekte ne anlama geldiğini ve nasıl çözüleceğini göreceksiniz.
Hata mesajları sayısal kodlarla birlikte gelir (genişletilmiş kodlar daha da spesifiktir). Loglarda iki biçimi de göreceksiniz:
Error: database is locked -- kod 5 (SQLITE_BUSY) - veritabanı kilitli
Error: unable to open database -- kod 14 (SQLITE_CANTOPEN) - veritabanı açılamıyor
Error: attempt to write a readonly -- kod 8 (SQLITE_READONLY) - salt okunura yazma denemesi
Error: database disk image is -- kod 11 (SQLITE_CORRUPT) - veritabanı disk imajı bozuk
Hata kodunu bilmek arama yaparken çok işe yarar — SQLITE_BUSY, sade İngilizce mesaja göre çok daha isabetli sonuçlar getirir.
database is locked (SQLITE_BUSY)
Birden fazla yerden yazma yapan uygulamalarda en sık karşılaşılan SQLite hatasıdır. SQLite yazma işlemlerini sıraya sokar: yazma kilidini aynı anda yalnızca tek bir bağlantı tutabilir. İkinci bir yazıcı, busy timeout süresi içinde kilidi alamazsa bu hatayı alırsınız.
Etki sırasına göre üç çözüm:
WAL modu, çoğu iş yükünde kilitlenme sorununu tek başına çözer. Busy timeout ise gerçek yazma çakışmalarına karşı güvenlik ağındır. Ayarların ötesinde, kodunuzu da gözden geçirin: program ağ I/O'su yaparken açık bırakılan bir transaction, kilidi tüm bu süre boyunca tutar. Transaction'ları kısa tutun ve iş biter bitmez COMMIT (ya da ROLLBACK) edin.
unable to open database file (SQLITE_CANTOPEN) hatası
SQLite dosyayı açmaya çalıştı ama işletim sistemi izin vermedi. Vakaların %95'inde sorun dosya yolunda veya bunu içeren dizindedir:
-- Kontrol edilecekler:
-- 1. Yol mevcut mu? ls -l /path/to/db.sqlite
-- 2. Üst dizin mevcut mu? SQLite dosyayı oluşturur
-- ancak üstündeki dizini oluşturmaz.
-- 3. İşleminizi çalıştıran kullanıcının dizinde (yalnızca dosyada
-- değil) okuma+yazma izni var mı?
-- 4. Birim bağlı mı, dolu değil mi ve salt okunur değil mi?
İnce bir ayrıntı: SQLite, veritabanının yanında yardımcı dosyalar (-journal, -wal, -shm) oluşturmak zorunda. Dosyanın kendisi yazılabilir olsa bile dizin yazılabilir değilse, bağlantı açılır ama yazma işlemleri patlar. Bu yüzden her zaman dizin düzeyinde yazma izni de verin.
attempt to write a readonly database (SQLITE_READONLY)
Bir öncekinin yakın akrabası olan bu sqlite readonly database hatası, dosya sorunsuz açıldığı halde yazma denemelerinin başarısız olduğu durumlarda karşımıza çıkar. Sebepleri, sıklık sırasına göre:
- İşletim sistemi kullanıcısının dosya ya da onun bulunduğu dizin üzerinde yazma izni yok.
- Bağlantı salt-okunur bir bayrakla açılmış (
SQLITE_OPEN_READONLYveya URI içindemode=ro). - Disk bölümü salt-okunur olarak bağlanmış (Docker bind mount'larında ve bazı bulut dosya sistemlerinde sıkça görülür).
- Veritabanı, SQLite'ın ihtiyaç duyduğu kilitleme mekanizmasını desteklemeyen bir ağ dosya sistemi üzerinde duruyor.
İzinleri düzeltin ya da birimi yeniden bağlayın. Docker kullanıyorsanız bind mount'un :ro olmadığından ve dizinin sahibinin container kullanıcısı olduğundan emin olun.
database disk image is malformed (SQLITE_CORRUPT)
Dosyanın byte'ları artık SQLite'ın beklediği biçimle uyuşmuyor. Bu hatanın gerçek sebepleri genelde çevresel: düzgün fsync yapmayan dosya sistemlerinde yazma sırasında öldürülen süreçler, aktif bir yazıcı varken veritabanını kopyalamak, donanım arızaları veya dosyayı Dropbox/iCloud üzerinden senkronize etmek.
Önce hasarı doğrulayın:
integrity_check ok döndürüyorsa veritabanınızda bir sorun yok demektir; hatanın kaynağı büyük ihtimalle başka bir yerdedir (çoğu zaman kapanmamış, eski bir bağlantıdır). Eğer komut bir sorun listesi döndürürse, kurtarma işlemine geçmeniz gerekir.
En temiz kurtarma yöntemi, CLI'nin .recover komutunu kullanmaktan geçer. Bu komut, kurtarabildiği tüm verileri yepyeni bir veritabanına aktarır:
sqlite3 corrupt.db ".recover" | sqlite3 recovered.db
sqlite3 recovered.db "PRAGMA integrity_check;"
Yakın tarihli bir yedeğiniz varsa, kurtarmaya uğraşmak yerine direkt yedekten geri dönün — hem daha hızlı olur hem de "verinin çoğunu kurtardık galiba" muğlaklığından kurtulursunuz. Çalışan bir veritabanını doğru şekilde kopyalamak için yedekleme ve geri yükleme sayfasına göz atın (ipucu: cp ile olmaz).
no such table ve no such column hataları
Bu hatalar tam olarak yazdığını söylüyor aslında, ama sebebi genelde iki şeyden biri oluyor: ya sandığınızdan farklı bir veritabanına bağlısınız ya da bir migration çalışmamış.
Uygulamanızın bağlantı dizesini (connection string) bir kontrol edin — göreli yollar, geçerli çalışma dizinine göre çözümlenir ve bu dizin terminalinizde, IDE'nizde ve production sürecinizde farklı olur. Bellek içi veritabanı (:memory:) ise her seferinde sıfırdan oluşturulur; kalıcı olmasını bekleyenleri çoğu zaman yanıltan nokta da budur.
Tanımlayıcıların (identifier) tırnak içine alınması da önemli bir ayrıntı. Tırnaksız isimler büyük/küçük harfe duyarsızdır, ama "User" ile "user" birbirinden farklı tanımlayıcılardır. Tabloyu adı tırnak içindeyken oluşturduysanız, sonrasında da tırnaklı kullanmaya devam etmeniz gerekir.
Constraint ihlalleri (constraint violations)
SQLite, bir kısıtlamayı (constraint) bozacak yazma işlemlerini reddeder. Hata mesajı, hangi kısıtlamanın ihlal edildiğini doğrudan belirtir:
Her bir hatanın arka planda farklı bir kodu var (SQLITE_CONSTRAINT_UNIQUE, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_NOTNULL). Çözüm neredeyse her zaman uygulama katmanında: ya veriyi yazmadan önce doğrulayın, ya da yinelenen kayıtları bilinçli yönetmek için INSERT ... ON CONFLICT kullanın.
FOREIGN KEY kısıtlaması başarısız oldu hatası ayrı bir parantezi hak ediyor: SQLite'ta foreign key kontrolü varsayılan olarak kapalıdır. Bunu açmazsanız geçersiz referanslar sessizce veritabanına girer, sonra zorlamayı açtığınız anda patlak verir. Bu yüzden pragma'yı her bağlantıda açmayı alışkanlık haline getirin:
cannot start a transaction within a transaction
Açık bir transaction varken tekrar BEGIN çağırdın. SQLite iç içe transaction'a izin vermez; ama aynı işi gören iç içe savepoint kullanımına izin verir:
If your ORM or framework manages transactions, you've probably told it to start one twice. Check whether autocommit is on, and whether your connection pool is reusing a connection that already has an open transaction.
disk I/O error (SQLITE_IOERR)
İşletim sistemi okuma ya da yazma isteğini reddetti. Disk dolmuş olabilir, ağ dosya sisteminde bir aksaklık yaşanmış olabilir veya dosya SQLite'ın altından silinmiş olabilir. İlk bakacağınız şey df -h. İkincisi ise veritabanının NFS ya da bulutla senkronize bir klasör gibi güvenilmez bir yerde duruyor olup olmadığı — SQLite, fsync çalışan yerel bir POSIX dosya sistemi varsayar. Taşıma şansınız yoksa, bozulma riskinin arttığını kabul etmeniz gerekir.
syntax error near "..."
SQLite'ın ayrıştırıcısı (parser), hangi token'da takıldığını size söyler. Çözüm genellikle hatanın gösterdiği yerden üç satır geride saklıdır: eksik bir virgül, anahtar kelimeyle çakışan tırnaksız bir tanımlayıcı ya da tek tırnakların düzgün kaçırılmadığı bir string ('it's' değil, 'it''s' olmalı).
Kullanıcıdan gelen verileri SQL'e elle eklemek yerine parametre bağlama (? yer tutucuları) kullanın — hem bir sürü sözdizimi hatasından hem de SQL injection riskinden tek hamlede kurtulursunuz.
Hızlı Tanı Kontrol Listesi
Üretim ortamında bir şeyler ters gittiğinde, aşağıdaki sıra vakaların büyük çoğunluğunu bir dakikadan kısa sürede çözmenize yetiyor:
Beş pragma, beş cevap. Başarısız sorgudan dönen hata koduyla birleştirdiğinizde, sorunun hangi kategoriye ait olduğunu ve dokümantasyonda hangi sayfayı açacağınızı kestirmek çok kolaylaşır.
Müfredatı Toparlarken
Tur burada bitiyor. CREATE TABLE ile başladık; join'ler, indeksler, transaction'lar, WAL modu, yedekleme derken SQLite'ı gerçek dünyaya çıkardığınızda karşınıza çıkan hata senaryolarına kadar geldik. Aslında örüntü hep aynı: transaction'ları kısa tutun, foreign key'leri açık bırakın, WAL modunu kullanın, düzenli yedek alın ve PRAGMA integrity_check komutuna hak ettiği saygıyı gösterin. Bu alışkanlıkları edinirseniz SQLite arka planda yıllarca sessiz sedasız çalışmaya devam eder.
Sıkça Sorulan Sorular
SQLite neden 'database is locked' hatası veriyor?
Başka bir bağlantı yazma kilidini tutuyor ve sizinki beklerken zaman aşımına uğruyor. Genelde üç şey işe yarar: PRAGMA journal_mode=WAL ile WAL moduna geçmek (böylece okuyucular yazıcıları bloklamaz), PRAGMA busy_timeout = 5000 ile bekleme süresini artırmak ve transaction'ları açık bırakmadan zamanında commit etmek.
SQLite'ta 'attempt to write a readonly database' hatası nasıl çözülür?
Bu hata neredeyse her zaman SQLite ile değil, dosya sistemi izinleriyle ilgilidir. Process'i çalıştıran işletim sistemi kullanıcısının hem veritabanı dosyasına hem de onu içeren dizine yazma yetkisi olması gerekir (SQLite oraya -journal veya -wal yan dosyaları oluşturur). Sahiplik (ownership), izin bitleri ve disk'in salt okunur (read-only) mount edilmediğinden emin olun.
'Database disk image is malformed' hatası ne anlama geliyor?
SQLite, beklediği formatla uyuşmayan byte'lar okumuş demektir — genellikle aniden öldürülen process'ler, bozuk diskler veya dosya açıkken kopyalanması yüzünden oluşan bir bozulmadır. Önce PRAGMA integrity_check ile doğrulayın, sonra CLI'daki .recover komutuyla kurtarılabilen kısmı yeni bir veritabanına dump edin. Yedeğiniz varsa restore etmek çok daha hızlıdır.
Neden 'no such table' veya 'no such column' hatası alıyorum?
Ya sandığınızdan farklı bir veritabanı dosyasına bağlısınız ya da migration çalışmamıştır. SQLite'ın gerçekte hangi dosyayı açtığını görmek için PRAGMA database_list, gerçek kolonları görmek için .schema tablename kullanın. Tanımlayıcılardaki yazım hataları ve büyük/küçük harf uyuşmazlıkları da yaygın bir sebep — SQLite tırnaksız isimlerde case-insensitive davranır ama tırnak içine alınmış isimlerde değil.