MySQL dan PostgreSQL berjalan sebagai server terpisah, tetapi SQLite mengambil jalan sebaliknya: seluruh mesin database berupa pustaka yang menyatu di dalam proses PHP Anda. Tidak ada server yang harus diinstal, tidak ada koneksi jaringan, dan seluruh database hidup dalam satu berkas — atau bahkan hanya di memori. Itulah alasan seluruh contoh pada section ini memakai SQLite: cukup new PDO("sqlite::memory:") dan laboratorium SQL Anda langsung siap. Pada chapter ini kita akan mengenal SQLite lebih dekat: bentuk DSN-nya, perilaku bawaannya yang penting, pengaturan lewat PRAGMA, sampai keputusan kapan SQLite pantas membawa aplikasi nyata ke produksi.

Database yang Menyatu dengan Aplikasi #

SQLite hadir dalam dua ekstensi PHP: pdo_sqlite (driver PDO, yang kita pakai sepanjang course ini) dan sqlite3 (API berorientasi object tersendiri). Build PHP standar umumnya menyertakan keduanya, dan Anda bisa memastikannya lewat extension_loaded():

php
<?php
var_dump(extension_loaded('pdo_sqlite'), extension_loaded('sqlite3'));

$pdo = new PDO('sqlite::memory:');
echo "SQLite versi " . $pdo->query('SELECT sqlite_version()')->fetchColumn() . "\n";
echo "driver PDO    : " . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . "\n";

Output yang Dihasilkan:

text
bool(true)
bool(true)
SQLite versi 3.45.2
driver PDO    : sqlite

Baris pertama mencetak bool(true) dua kali, yang berarti kedua ekstensi aktif pada build ini. Query SELECT sqlite_version() menunjukkan versi pustaka SQLite yang dikompilasi bersama PHP — angka ini menentukan fitur apa yang tersedia, misalnya klausa RETURNING baru hadir pada SQLite 3.35. Atribut PDO::ATTR_DRIVER_NAME berguna saat satu kode harus tahu database apa yang sedang dipakai, misalnya untuk memilih sintaks dialek yang berbeda antar database.

DSN SQLite: Berkas dan :memory: #

Bentuk DSN driver pdo_sqlite hanya dua: sqlite:/path/ke/berkas untuk database berkas, dan sqlite::memory: untuk database yang hidup di memori saja. Database berkas bertahan melewati akhir skrip — inilah bentuk yang dipakai aplikasi nyata:

php
<?php
$berkas = '/tmp/belajar-toko.sqlite';

$pdo = new PDO("sqlite:$berkas");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("CREATE TABLE produk (id INTEGER PRIMARY KEY, nama TEXT)");
$pdo->exec("INSERT INTO produk (nama) VALUES ('Kopi Hitam')");

unset($pdo); // tutup koneksi, data tidak hilang

$pdoBaru = new PDO("sqlite:$berkas");
echo $pdoBaru->query('SELECT COUNT(*) FROM produk')->fetchColumn() . " baris tersimpan\n";
echo "produk pertama: " . $pdoBaru->query('SELECT nama FROM produk LIMIT 1')->fetchColumn() . "\n";

unset($pdoBaru);
unlink($berkas);

Output yang Dihasilkan:

text
1 baris tersimpan
produk pertama: Kopi Hitam

Skrip di atas membuka koneksi, membuat tabel, menyimpan satu baris, lalu melepas objek $pdo dengan unset() — dan baris tersebut tetap ada ketika koneksi kedua dibuka. Berkas .sqlite itulah seluruh database Anda: mudah disalin untuk backup, dipindah antar mesin, atau dilampirkan pada laporan bug. Pada contoh, unlink() di akhir sekadar membersihkan berkas latihan; aplikasi nyata justru menjaganya. Sebaliknya, sqlite::memory: menciptakan database kosong baru di setiap eksekusi dan menghapus semuanya saat objek PDO dihancurkan — sempurna untuk pengujian dan contoh buku, seperti yang kita lakukan sepanjang section ini.

Alternatif Tanpa PDO: Ekstensi SQLite3 #

Jika aplikasi Anda pasti selalu memakai SQLite dan tidak butuh kebebasan berganti driver, ekstensi sqlite3 menawarkan API yang lebih tipis. Kelas SQLite3-nya menyediakan prepare(), bindValue(), dan execute() dengan konstanta sendiri seperti SQLITE3_TEXT:

php
<?php
$db = new SQLite3(':memory:');
$db->exec('CREATE TABLE kota (nama TEXT)');

$stmt = $db->prepare('INSERT INTO kota (nama) VALUES (?)');
$stmt->bindValue(1, 'Bandung', SQLITE3_TEXT);
$stmt->execute();
$stmt->bindValue(1, 'Surabaya', SQLITE3_TEXT);
$stmt->execute();

$hasil = $db->query('SELECT nama FROM kota ORDER BY nama');
while ($baris = $hasil->fetchArray(SQLITE3_ASSOC)) {
    echo $baris['nama'] . "\n";
}

Output yang Dihasilkan:

text
Bandung
Surabaya

Method fetchArray(SQLITE3_ASSOC) setara fetch(PDO::FETCH_ASSOC) milik PDO. Untuk proyek baru, PDO tetap pilihan utama: satu API yang sama untuk SQLite, MySQL, dan PostgreSQL berarti kode Anda tidak terkunci pada satu database, dan seluruh pengetahuan dari chapter-chapter sebelumnya tetap terpakai. Ekstensi SQLite3 lebih sering dipakai untuk skrip kecil atau saat fitur khusus SQLite (seperti membuka berkas hanya-baca) dibutuhkan secara langsung.

PRAGMA: Mengatur Perilaku SQLite #

SQLite menyimpan pengaturan konfigurasinya lewat perintah khusus bernama PRAGMA, bukan lewat berkas konfigurasi terpisah. Dua pragma paling penting bagi aplikasi PHP adalah foreign_keys dan busy_timeout — dan keduanya menyimpan kejutan: foreign_keys tidak aktif secara bawaan:

php
<?php
$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

echo "foreign_keys bawaan: " . $pdo->query('PRAGMA foreign_keys')->fetchColumn() . "\n";

$pdo->exec('PRAGMA foreign_keys = ON');
echo "setelah PRAGMA ON  : " . $pdo->query('PRAGMA foreign_keys')->fetchColumn() . "\n";

$pdo->exec('CREATE TABLE kategori (id INTEGER PRIMARY KEY, nama TEXT)');
$pdo->exec('CREATE TABLE produk (
    id INTEGER PRIMARY KEY,
    nama TEXT,
    kategori_id INTEGER REFERENCES kategori(id)
)');

$pdo->exec('PRAGMA busy_timeout = 5000');
echo "busy_timeout: " . $pdo->query('PRAGMA busy_timeout')->fetchColumn() . " ms\n";

try {
    $pdo->exec("INSERT INTO produk (nama, kategori_id) VALUES ('Kopi Hitam', 99)");
} catch (PDOException $e) {
    echo "ditolak: " . $e->getMessage() . "\n";
}

Output yang Dihasilkan:

text
foreign_keys bawaan: 0
setelah PRAGMA ON  : 1
busy_timeout: 5000 ms
ditolak: SQLSTATE[23000]: Integrity constraint violation: 19 FOREIGN KEY constraint failed

Mari kita uraikan perilaku penting pada contoh di atas:

  1. PRAGMA foreign_keys pertama mencetak 0 — batasan kunci asing dinyalakan per koneksi, dan koneksi baru selalu mulai dari mati. Tanpa PRAGMA foreign_keys = ON, INSERT dengan kategori_id = 99 akan berhasil diam-diam meskipun kategori 99 tidak ada.
  2. PRAGMA busy_timeout = 5000 memberi tahu SQLite untuk menunggu hingga 5000 milidetik ketika berkas terkunci proses lain, alih-alih langsung gagal dengan galat database is locked. Pada aplikasi web yang datang dengan beberapa proses PHP bersamaan, satu baris ini menghilangkan sebagian besar galat sesaat.
  3. INSERT dengan kategori_id = 99 kini ditolak PDOException karena foreign key aktif dan kategori 99 memang tidak ada — perilaku yang kita andalkan untuk menjaga integritas data.

Mode Jurnal WAL untuk Akses Bersamaan #

Secara bawaan, SQLite memakai mode jurnal delete: setiap penulisan mengunci berkas database sehingga pembaca lain harus menunggu. Mode WAL (Write-Ahead Logging) mengubah arsitekturnya — perubahan ditulis dulu ke berkas -wal terpisah, sehingga pembaca tetap bisa membaca versi data yang stabil selama penulisan berlangsung:

php
<?php
$berkas = '/tmp/belajar-wal.sqlite';
$pdo = new PDO("sqlite:$berkas");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

echo "journal_mode awal: " . $pdo->query('PRAGMA journal_mode')->fetchColumn() . "\n";
echo "pindah ke WAL    : " . $pdo->query('PRAGMA journal_mode = WAL')->fetchColumn() . "\n";

$pdo->exec('CREATE TABLE log_aplikasi (pesan TEXT)');
$pdo->exec("INSERT INTO log_aplikasi (pesan) VALUES ('aplikasi dimulai')");
echo "isi log: " . $pdo->query('SELECT pesan FROM log_aplikasi')->fetchColumn() . "\n";

unset($pdo);
foreach (['', '-wal', '-shm'] as $akhiran) {
    @unlink($berkas . $akhiran);
}

Output yang Dihasilkan:

text
journal_mode awal: delete
pindah ke WAL    : wal
isi log: aplikasi dimulai

Query PRAGMA journal_mode = WAL mengembalikan mode yang sedang aktif setelah perintah dijalankan — wal berarti perpindahan berhasil. Setelah itu, dua berkas pendamping (-wal dan -shm) muncul di samping berkas utama; keduanya normal, otomatis digabungkan kembali saat semua koneksi menutup, dan skrip contoh kita membersihkannya di akhir. Mode WAL hanya berlaku untuk database berkas: pada :memory:, SQLite menjawab memory dan tetap memakai jurnal internal memori. Untuk aplikasi web PHP dengan beberapa proses pekerja, kombinasi WAL + busy_timeout adalah pengaturan standar yang layak Anda jadikan bawaan.

Kapan SQLite Layak untuk Produksi #

SQLite bukan mainan — situs bertraffic sedang, aplikasi desktop, dan perangkat IoT memakainya di produksi dalam jumlah sangat besar. Kekuatannya yang sulit ditandingi: tanpa server untuk dirawat, backup berupa penyalinan satu berkas, dan kinerja baca yang sangat cepat karena tidak ada lompatan jaringan. Batasannya pun jelas, dan tabel berikut membantu Anda memutuskan:

AspekKondisi SQLiteKondisi MySQL/PostgreSQL
Pola tulisdominan baca, tulisan jarangbanyak penulis konkuren
Aksessatu mesin, berkas lokalbanyak mesin lewat jaringan
Pengguna databasetidak ada — izin berkas adalah batasnyaakun dan hak akses per pengguna
Perawatanhampir nolserver, backup, monitoring
Ukuran datanyaman hingga ratusan GBdirancang untuk skala lebih besar

Dua larangan praktis perlu Anda ingat. Pertama, jangan pernah menaruh berkas SQLite di berkas jaringan seperti NFS atau SMB — mekanisme penguncian SQLite tidak andal di sana dan berkas bisa rusak. Kedua, walau mode WAL membolehkan pembaca dan satu penulis bekerja bersamaan, penulisan tetap diserialisasi: jika aplikasi Anda menulis puluhan baris per detik dari banyak proses, antrean penguncian akan terasa dan saatnya pindah ke database server.

Ringkasan #

SQLite menyatukan database ke dalam proses PHP: DSN sqlite::memory: untuk pengujian, sqlite:berkas untuk data yang bertahan. Pragma foreign_keys = ON wajib dinyalakan di setiap koneksi karena tidak aktif secara bawaan, busy_timeout meredam galat kunci sesaat, dan mode WAL membuat pembaca tidak lagi menunggu penulis. Untuk aplikasi yang didominasi pembacaan di satu mesin, SQLite adalah pilihan produksi yang sah dan hemat. Namun ketika aplikasi tumbuh — banyak penulis konkuren, banyak server aplikasi, kebutuhan akun terpisah — database server menjadi kebutuhan, dan yang paling populer di dunia PHP adalah MySQL, yang kita bahas pada chapter berikutnya.

Referensi #