Bağlantı Aslında Sadece Açık Bir Dosya
SQLite'ın sunucusu yoktur. Dinleyen bir daemon, bağlanılacak bir host, pazarlanacak kimlik bilgileri... Hiçbiri yok. "Bağlanmak" demek, sürücünün diskte bir dosyayı açıp sayfalarını okuyup yazmaya başlaması demek. Zihinsel model bu kadar basit.
Her dilin SQLite'ın C kütüphanesini saran bir sürücüsü var. Şekilleri farklı görünse de hareketli parçalar aynı: veritabanı dosyasının yolu, bir açma çağrısı, sorguları çalıştıracağınız bir handle ve işiniz bittiğinde bir kapatma çağrısı.
-- Kavramsal olarak, her sürücü şunu yapar:
-- 1. Verilen yoldaki dosyayı açar veya oluşturur.
-- 2. Bir handle (tanıtıcı) edinir.
-- 3. Hazırlanmış ifadeler aracılığıyla SQL çalıştırır.
-- 4. Handle'ı kapatır.
The rest of this page is what that looks like in real code, and the few settings worth setting before your first query.
Python: Standart Kütüphanedeki sqlite3
Python, kutudan çıktığı haliyle sqlite3 modülünü getiriyor — ekstra bir kurulum yok. Temel kullanım şöyle:
-- Python
import sqlite3
conn = sqlite3.connect("app.db")
conn.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)")
conn.execute("INSERT INTO notes (body) VALUES (?)", ("ilk not",))
conn.commit()
for row in conn.execute("SELECT id, body FROM notes"):
print(row)
conn.close()
Bilmekte fayda olan birkaç nokta:
sqlite3.connect("app.db")dosya yoksa otomatik oluşturur. Yalnızca RAM'de yaşayan bir veritabanı için":memory:"parametresini geçin.sqlite3.connect("file:app.db?mode=ro", uri=True)ifadesi, URI biçimini kullanarak veritabanını salt okunur açar.- SQL içindeki
?bir yer tutucudur — string birleştirme yerine mutlaka parametre binding kullanın. Sonraki bölümde bu konuyu daha ayrıntılı işleyeceğiz. conn.commit()çağrısı zorunludur; tabii context manager (with conn:) kullanmıyorsanız — bu yapı commit işlemini otomatik halleder.
Uzun süre çalışan uygulamalarda, eşzamanlı yazıcıların hata vermek yerine sırasını beklemesi için bir busy timeout ayarlayın:
-- Python
conn.execute("PRAGMA busy_timeout = 5000") -- en fazla 5 saniye bekle
conn.execute("PRAGMA journal_mode = WAL") -- daha iyi eşzamanlılık
Node.js: better-sqlite3
Node ekosisteminde birkaç seçenek var ama ekiplerin çoğunlukla tercih ettiği kütüphane better-sqlite3. Senkron çalışıyor — kulağa Node için ters gelse de SQLite tarafında aslında daha hızlı, çünkü sorgular mikrosaniyeler içinde dönüyor.
-- Node.js
const Database = require("better-sqlite3");
const db = new Database("app.db");
db.exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
const insert = db.prepare("INSERT INTO notes (body) VALUES (?)");
insert.run("ilk not");
const rows = db.prepare("SELECT id, body FROM notes").all();
console.log(rows);
db.close();
db.prepare(...) yeniden kullanılabilir bir statement nesnesi döndürür. Yazma işlemleri için .run(), tüm satırları almak için .all(), tek satır için .get() kullanılır. Çoğu SQL sürücüsündeki mantıkla aynı.
Açılışta pragma ayarlarını yapın:
-- Node.js
db.pragma("journal_mode = WAL");
db.pragma("busy_timeout = 5000");
db.pragma("foreign_keys = ON"); -- varsayılan olarak kapalıdır, neredeyse her zaman istenir
foreign_keys = ON ayarına özellikle dikkat çekmek lazım: SQLite, siz açıkça istemedikçe foreign key kısıtlamalarını uygulamaz ve bu ayar her bağlantı için ayrı ayrı yapılır. Unutursanız, REFERENCES ifadeleriniz tamamen süs olarak kalır.
Go: Sürücü ile database/sql kullanımı
Go'nun standart database/sql paketi sürücüden bağımsız çalışır. SQLite için en yaygın iki tercih var: modernc.org/sqlite (saf Go, CGO gerektirmez) ve github.com/mattn/go-sqlite3 (CGO tabanlı).
-- Go
import (
"database/sql"
_ "modernc.org/sqlite"
)
db, err := sql.Open("sqlite", "app.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)")
if err != nil { panic(err) }
defer db.Close()
_, err = db.Exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)")
_, err = db.Exec("INSERT INTO notes (body) VALUES (?)", "ilk not")
rows, _ := db.Query("SELECT id, body FROM notes")
defer rows.Close()
for rows.Next() {
var id int; var body string
rows.Scan(&id, &body)
fmt.Println(id, body)
}
Dosya adından sonra gelen query string, sürücünün bağlantı sırasında pragma'ları geçirme şekli — format sürücüye göre değişiyor, o yüzden hangisini seçerseniz onun dokümanına bakın.
sql.Open aslında bir bağlantı açmaz; ilk sorgu açar. db bir bağlantı havuzudur. SQLite için küçük bir havuz (hatta yazma ağırlıklı iş yüklerinde db.SetMaxOpenConns(1)) genelde doğru tercihtir.
Java: JDBC bağlantısı
Standart sürücü org.xerial:sqlite-jdbc'dir. JDBC URL'leri şu şekilde görünür: jdbc:sqlite:<path>:
-- Java
import java.sql.*;
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:app.db")) {
try (Statement st = conn.createStatement()) {
st.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
st.execute("PRAGMA journal_mode = WAL");
st.execute("PRAGMA busy_timeout = 5000");
}
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO notes (body) VALUES (?)")) {
ps.setString(1, "ilk not");
ps.executeUpdate();
}
try (PreparedStatement ps = conn.prepareStatement("SELECT id, body FROM notes");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) System.out.println(rs.getInt(1) + " " + rs.getString(2));
}
}
Bellek içi (in-memory): jdbc:sqlite::memory:. Salt okunur açmak için ya ?open_mode=1 ekleyin ya da SQLiteConfig nesnesi kullanın.
PHP: PDO ile SQLite bağlantısı
PDO'da SQLite için DSN formatı sqlite:<yol> şeklindedir:
-- PHP
$db = new PDO("sqlite:app.db");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
$db->exec("PRAGMA journal_mode = WAL");
$db->exec("PRAGMA busy_timeout = 5000");
$stmt = $db->prepare("INSERT INTO notes (body) VALUES (?)");
$stmt->execute(["ilk not"]);
foreach ($db->query("SELECT id, body FROM notes") as $row) {
echo $row["id"] . " " . $row["body"] . "\n";
}
sqlite::memory: ile bellek içi (in-memory) veritabanı kullanabilirsiniz. ATTR_ERRMODE değerini mutlaka exception fırlatacak şekilde ayarla — sessiz hatalarla uğraşmak gerçekten can sıkıcıdır.
SQLite Connection String ve Dosya Yolları
Sürücüden sürücüye değişse de "connection string"in iki temel hâliyle karşılaşırsınız:
- Düz yol:
app.db,./data/app.db,/var/lib/myapp/app.db. Göreli (relative) yollar, sürecin çalışma dizinine göre çözümlenir — ki bu üretim ortamında nadiren istediğiniz şeydir. Mutlak yolları tercih et. - URI biçimi:
file:app.db?mode=rwc&cache=shared. Bu format sayesindemode=ro(salt okunur),mode=rwc(oku-yaz-oluştur, varsayılan),cache=sharedvenolock=1gibi bayrakları ayarlayabilirsiniz.
Karşına çıkacak bazı özel değerler:
:memory:— özel (private) bir bellek içi veritabanı. Her bağlantının kendine ait bir tane olur.file::memory:?cache=shared— aynı süreç içindeki birden fazla bağlantının paylaşabildiği bir bellek içi veritabanı.""(boş string) — kapatıldığında silinen, geçici ve özel bir disk üstü veritabanı.
JDBC, URI'nin başına jdbc:sqlite: ekler. PDO sqlite: kullanır. Go sürücüleri ve Python'ın sqlite3 modülü ise yolu ya da URI'yi doğrudan kabul eder.
Connection Pool İşin İçine Girince?
SQLite tek yazıcılı (single-writer) bir veritabanıdır. Herhangi bir anda yazma kilidini yalnızca tek bir bağlantı tutar; diğerleri sırasını bekler. Birden fazla yazıcıyı havuza atmak yazma işlemlerini hızlandırmaz — sadece aynı kilit için daha fazla aday yaratır.
Yine de küçük bir havuz şu durumlarda işe yarar:
- WAL modunda eş zamanlı okumalar için; okuyucular ne birbirini ne de yazıcıyı bloklar.
- Yavaş bir sorgunun tüm uygulamayı tıkamasını engellemek, yani head-of-line blocking'i önlemek için.
Bir web uygulaması için makul varsayılanlar:
- WAL modu açık olsun.
- Birkaç saniyelik bir
busy_timeout, böylece çakışmalar hata vermek yerine kibarca beklesin. - Havuz boyutu 1 yazıcı + N okuyucu olsun; trafik hafifse tek bir paylaşımlı bağlantı bile yeter.
- Foreign key kontrolü her bağlantıda açık olsun.
-- Bunları her yeni bağlantıda uygulayın:
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
PRAGMA synchronous = NORMAL; -- WAL ile güvenlidir; FULL'dan daha hızlıdır
synchronous = NORMAL ayarı WAL ile birlikte kullanılan klasik bir kombinasyondur — uygulama çökmelerine karşı veriyi korur, işletim sistemi çökmelerinde biraz daha gevşek davranır ve varsayılan FULL ayarına göre belirgin şekilde daha hızlıdır.
SQLite Bağlantısını Kapatmak (ve Neden Önemli?)
Her sürücünün bir kapatma çağrısı vardır: conn.close(), db.Close(), db.close(). Bağlantıyı kapatmamak dosya tanıtıcılarının (file descriptor) sızmasına yol açar ve WAL dosyasının şişmesine neden olabilir.
Uzun süre çalışan servislerde daha yaygın yaklaşım, her istekte aç-kapa yapmak yerine süreç boyunca tek bir bağlantı (veya bir havuz) kullanmaktır. SQLite bağlantısı açmak ucuz bir işlemdir, ama her seferinde pragma'ları yeniden uygulamak hem gereksiz iş yükü yaratır hem de unutulması kolaydır.
-- Python — süreç başına bir bağlantı, istekler arasında yeniden kullanılır
DB = sqlite3.connect("app.db", check_same_thread=False)
DB.execute("PRAGMA journal_mode = WAL")
DB.execute("PRAGMA busy_timeout = 5000")
DB.execute("PRAGMA foreign_keys = ON")
Python tarafında özel bir not: Bağlantıyı birden fazla thread'den kullanacaksanız check_same_thread=False parametresini geçmeniz gerekir. Ayrıca çağrıları sıraya sokmak için bir lock veya bağlantı havuzu kullanmak isteyeceksiniz.
Canlıya Çıkmadan Önce Kontrol Listesi
Gerçek trafiği bir SQLite veritabanına yönlendirmeden önce şunları gözden geçirin:
- Veritabanı dosyası için mutlak (absolute) yol kullanın.
- WAL modunu açın (
PRAGMA journal_mode = WAL). busy_timeoutdeğerini 2-10 saniye arasında ayarlayın — bu,database is lockedhatasının önüne geçmenize yardımcı olur.- Her bağlantıda foreign key desteğini açın.
- Parametre bağlama ile prepared statement kullanın; asla string birleştirme yapmayın.
- Veritabanı dosyasının bulunduğu dizinin process tarafından yazılabilir olduğundan emin olun (WAL modunda SQLite, ana dosyanın yanına
-walve-shmdosyaları yazar). - Yedeklemeyi ihtiyacınız olmadan önce planlayın —
VACUUM INTOve.backupkomutunu ileride ele alacağız.
Sıradaki Konu: Migration'lar
Bağlantı kurmak işin kolay tarafı. Asıl zor olan, production veritabanını elle kurcalamadan şemayı zaman içinde evrilten yapıyı kurmak. Migration'lar tam da burada devreye giriyor: ALTER TABLE komutlarını tekrarlanabilir ve sürüm kontrolüne alınmış bir sürece dönüştürmenizi sağlıyor. Bir sonraki sayfada bunu inceleyeceğiz.
Sıkça Sorulan Sorular
Koddan SQLite veritabanına nasıl bağlanırım?
Sürücüye bir dosya yolu vermen yeterli. Python tarafında sqlite3.connect('app.db'), Node'da better-sqlite3 ile new Database('app.db'), Go'da ise sql.Open("sqlite", "app.db") şeklinde. SQLite'ın sunucusu olmadığı için aslında 'bağlanmak' demek, sadece bir dosyayı açmak demek — dosya yoksa SQLite kendisi oluşturuyor.
SQLite connection string nasıl görünür?
Çoğu sürücü hem düz dosya yolunu (./data/app.db) hem de URI biçimini (file:app.db?mode=rwc&cache=shared) kabul eder. URI biçimi sayesinde salt okunur mod, paylaşımlı cache veya :memory: veritabanı gibi bayrakları ayarlayabilirsin. JDBC'de format jdbc:sqlite:app.db, PDO'da ise sqlite:app.db şeklinde.
SQLite'ta connection pool'a gerçekten ihtiyacım var mı?
Postgres veya MySQL'deki anlamda genelde gerek yok. SQLite yazma işlemlerini veritabanı düzeyinde sıraya soktuğu için yazıcı havuzu sana hız kazandırmaz. Eşzamanlı okumalar için, özellikle WAL modunda, küçük bir havuz işe yarayabilir. Pek çok uygulama tek bir paylaşımlı bağlantı + PRAGMA journal_mode=WAL + makul bir busy_timeout ile gayet rahat çalışır.
'database is locked' hatasından nasıl kurtulurum?
Önce bir busy timeout ayarla; böylece sürücü hemen patlamak yerine bekler: PRAGMA busy_timeout = 5000 (milisaniye cinsinden). Ardından PRAGMA journal_mode=WAL ile WAL modunu aç ki okuyucular yazıcıları bloklamasın. Transaction'ları kısa tut ve yazma transaction'ı açıkken yavaş, veritabanı dışı işler yapma.