Koneksi Hanyalah File yang Terbuka
SQLite tidak punya server. Tidak ada daemon yang mendengarkan di sebuah port, tidak ada host untuk dihubungi, tidak ada kredensial untuk dinegosiasikan. "Terhubung" berarti driver-mu membuka sebuah file di disk lalu mulai membaca dan menulis page-nya. Itulah gambaran lengkapnya.
Setiap bahasa punya driver yang membungkus library C SQLite. Bentuknya berbeda-beda, tapi bagian-bagiannya sama: path ke file database, pemanggilan open, handle untuk menjalankan pernyataan, dan pemanggilan close saat kamu selesai.
-- Conceptually, every driver does this:
-- 1. Open or create the file at the given path.
-- 2. Acquire a handle.
-- 3. Run SQL via prepared statements.
-- 4. Close the handle.
Bagian selanjutnya dari halaman ini menunjukkan seperti apa itu di kode sungguhan, ditambah beberapa pengaturan yang perlu dipasang sebelum query pertamamu.
Python: sqlite3 di Standard Library
Python sudah menyertakan sqlite3, tanpa perlu instalasi. Bentuk dasarnya:
-- 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 (?)", ("first note",))
conn.commit()
for row in conn.execute("SELECT id, body FROM notes"):
print(row)
conn.close()
Beberapa hal yang perlu diketahui:
sqlite3.connect("app.db")membuat file kalau belum ada. Berikan":memory:"untuk database yang hanya hidup di RAM.sqlite3.connect("file:app.db?mode=ro", uri=True)membuka database sebagai read-only lewat bentuk URI.?di SQL adalah placeholder: pakai parameter binding, jangan pernah penyambungan string. Bab berikutnya membahas ini lebih dalam.conn.commit()wajib dipanggil, kecuali kamu memakai context manager (with conn:) yang melakukan commit otomatis.
Untuk aplikasi yang berjalan lama, atur busy timeout agar penulis yang berjalan bersamaan menunggu, bukan error:
-- Python
conn.execute("PRAGMA busy_timeout = 5000") -- wait up to 5s
conn.execute("PRAGMA journal_mode = WAL") -- better concurrency
Node.js: better-sqlite3
Ekosistem Node punya beberapa pilihan, tapi better-sqlite3 adalah yang paling sering dipilih tim. Library ini sinkron (kedengarannya salah untuk Node, tapi sebenarnya lebih cepat untuk SQLite, karena query-nya selesai dalam hitungan mikrodetik).
-- 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("first note");
const rows = db.prepare("SELECT id, body FROM notes").all();
console.log(rows);
db.close();
db.prepare(...) mengembalikan objek pernyataan yang bisa dipakai ulang. .run() untuk penulisan, .all() mengembalikan semua baris, .get() mengembalikan satu baris. Polanya sama seperti sebagian besar driver SQL.
Atur pragma saat startup:
-- Node.js
db.pragma("journal_mode = WAL");
db.pragma("busy_timeout = 5000");
db.pragma("foreign_keys = ON"); -- off by default, almost always wanted
foreign_keys = ON layak disorot: SQLite tidak menegakkan foreign key kecuali kamu memintanya, per koneksi. Kalau kamu lupa, klausa REFERENCES milikmu hanya jadi hiasan.
Go: database/sql dengan Driver
Package standar database/sql di Go tidak terikat pada driver tertentu. Untuk SQLite, modernc.org/sqlite (murni Go, tanpa CGO) dan github.com/mattn/go-sqlite3 (CGO) adalah pilihan yang umum.
-- 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 (?)", "first note")
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)
}
Query string setelah nama file adalah cara driver ini mengirim pragma saat koneksi dibuat. Formatnya berbeda di setiap driver, jadi periksa dokumentasi driver yang kamu pilih.
sql.Open sebenarnya tidak membuka koneksi; query pertama yang membukanya. db adalah connection pool. Untuk SQLite, pool kecil (atau bahkan db.SetMaxOpenConns(1) untuk beban kerja yang banyak menulis) biasanya sudah tepat.
Java: JDBC
Driver org.xerial:sqlite-jdbc adalah standarnya. URL JDBC berbentuk 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, "first note");
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));
}
}
In-memory: jdbc:sqlite::memory:. Read-only: tambahkan ?open_mode=1 atau pakai objek SQLiteConfig.
PHP: PDO
DSN SQLite di PDO adalah sqlite:<path>:
-- 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(["first note"]);
foreach ($db->query("SELECT id, body FROM notes") as $row) {
echo $row["id"] . " " . $row["body"] . "\n";
}
sqlite::memory: untuk database in-memory. Selalu atur ATTR_ERRMODE ke exception, karena kegagalan yang diam sulit di-debug.
Connection String dan Path File
Di berbagai driver, kamu akan melihat dua jenis "connection string":
- Path biasa:
app.db,./data/app.db,/var/lib/myapp/app.db. Path relatif dihitung terhadap direktori kerja proses, dan itu jarang yang kamu inginkan di production. Lebih baik pakai path absolut. - Bentuk URI:
file:app.db?mode=rwc&cache=shared. Memungkinkanmu mengatur flag sepertimode=ro(read-only),mode=rwc(baca, tulis, buat; ini bawaannya),cache=shared, dannolock=1.
Nilai khusus yang akan kamu temui:
:memory:: database in-memory pribadi. Setiap koneksi mendapat database sendiri.file::memory:?cache=shared: database in-memory yang bisa dipakai bersama oleh beberapa koneksi di proses yang sama.""(string kosong): database sementara pribadi di disk yang dihapus saat ditutup.
JDBC memberi prefiks jdbc:sqlite: pada URI. PDO memakai sqlite:. Driver Go dan sqlite3 di Python menerima path atau URI secara langsung.
Bagaimana dengan Connection Pool?
SQLite adalah database dengan satu penulis. Pada satu waktu, tepat satu koneksi yang memegang write lock, dan yang lain menunggu. Membuat pool berisi banyak penulis tidak membuat penulisan lebih cepat, hanya menambah jumlah yang berebut lock yang sama.
Meski begitu, pool kecil berguna untuk:
- Pembacaan bersamaan dalam mode WAL, ketika pembaca tidak saling memblokir dan tidak memblokir penulis.
- Menghindari antrean yang tersumbat, ketika satu query lambat menahan seluruh aplikasi.
Pengaturan bawaan yang masuk akal untuk aplikasi web:
- Mode WAL aktif.
busy_timeoutbeberapa detik agar perebutan lock menunggu dengan sopan, bukan error.- Ukuran pool 1 penulis + N pembaca, atau cukup satu koneksi bersama kalau lalu lintasnya ringan.
- Foreign key dinyalakan di setiap koneksi.
-- Apply these on every new connection:
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
PRAGMA synchronous = NORMAL; -- safe with WAL; faster than FULL
synchronous = NORMAL adalah pasangan yang umum untuk WAL: tahan terhadap crash aplikasi, sedikit lebih longgar terhadap crash sistem operasi, dan terasa lebih cepat daripada bawaan FULL.
Menutup Koneksi (dan Kenapa Itu Penting)
Setiap driver punya pemanggilan close: conn.close(), db.Close(), db.close(). Tidak menutup koneksi membuat file descriptor bocor dan bisa membuat file WAL terus membesar.
Di layanan yang berjalan lama, pola yang lebih umum adalah satu koneksi (atau pool) selama proses hidup, bukan membuka dan menutup koneksi di setiap request. Membuka koneksi SQLite itu murah, tapi menerapkan ulang pragma setiap kali itu boros dan mudah terlupa.
-- Python - connection per process, reused across requests
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")
Khusus untuk Python, check_same_thread=False dibutuhkan kalau kamu akan memakai koneksi dari beberapa thread, dan kamu perlu lock atau pool untuk menjalankan pemanggilan secara berurutan.
Daftar Periksa Sebelum Rilis
Sebelum mengarahkan lalu lintas sungguhan ke database SQLite:
- Pakai path absolut untuk file database.
- Aktifkan mode WAL (
PRAGMA journal_mode = WAL). - Atur
busy_timeoutantara 2 sampai 10 detik. - Nyalakan foreign key di setiap koneksi.
- Pakai prepared statement dengan parameter binding, jangan pernah menyisipkan nilai ke string.
- Pastikan direktori tempat database berada bisa ditulisi oleh proses (dalam mode WAL, SQLite menulis file
-waldan-shmdi samping file utama). - Pikirkan backup sebelum kamu membutuhkannya.
VACUUM INTOdan perintah.backupdibahas nanti.
Berikutnya: Migrasi
Menghubungkan adalah bagian yang mudah. Bagian yang lebih sulit adalah mengembangkan skema dari waktu ke waktu tanpa mengedit database production secara manual. Migrasi adalah cara mengubah ALTER TABLE menjadi proses yang bisa diulang dan dikendalikan versinya. Itu halaman berikutnya.
Pertanyaan yang sering diajukan
Bagaimana cara menghubungkan database SQLite dari kode?
Arahkan driver-mu ke sebuah path file. Di Python, caranya sqlite3.connect('app.db'); di Node, new Database('app.db') dengan better-sqlite3; di Go, sql.Open("sqlite", "app.db"). SQLite tidak punya server, jadi 'koneksi' sebenarnya hanyalah membuka sebuah file. Kalau file-nya belum ada, SQLite akan membuatnya.
Seperti apa connection string SQLite?
Sebagian besar driver menerima path file biasa (./data/app.db) atau bentuk URI (file:app.db?mode=rwc&cache=shared). Bentuk URI memungkinkanmu mengatur flag seperti mode read-only, shared cache, atau database :memory:. JDBC memakai jdbc:sqlite:app.db; PDO memakai sqlite:app.db.
Apakah saya butuh connection pool untuk SQLite?
Biasanya tidak dengan cara yang sama seperti Postgres atau MySQL. SQLite menjalankan penulisan secara berurutan di tingkat database, jadi pool berisi banyak penulis tidak mempercepat apa pun. Pool kecil membantu untuk pembacaan yang berjalan bersamaan, terutama dalam mode WAL. Banyak aplikasi berjalan lancar dengan satu koneksi bersama ditambah PRAGMA journal_mode=WAL dan busy_timeout yang masuk akal.
Bagaimana cara menghindari error 'database is locked'?
Atur busy timeout agar driver-mu menunggu, bukan langsung gagal: PRAGMA busy_timeout = 5000 (dalam milidetik). Aktifkan mode WAL dengan PRAGMA journal_mode=WAL agar pembaca tidak memblokir penulis. Buat transaksi tetap singkat, dan jangan biarkan transaksi tulis terbuka sambil mengerjakan hal lambat di luar database.