Atomicity menuntut hal yang mustahil bagi berkas: saat COMMIT berlangsung, data lama dan data baru harus bertransisi tanpa pernah ada keadaan setengah jadi yang bisa dilihat. Trik SQLite adalah menulis di dua tempat — perubahan di satu berkas, cadangan keadaan lama di berkas lain yang disebut journal. Chapter ini membedah mekanisme rollback journal yang menjadi default, daftar lengkap journal_mode, lalu WAL: arsitektur alternatif yang membuat pembaca dan penulis tidak lagi saling menunggu.
Skema Contoh #
Seluruh contoh memakai tabel produk di berkas toko.db:
CREATE TABLE produk (
id INTEGER PRIMARY KEY,
nama TEXT NOT NULL,
stok INTEGER NOT NULL
);
INSERT INTO produk VALUES (1, 'Kopi Gayo 200g', 40);Setiap demo di chapter ini membuat ulang tabel tersebut secara mandiri, jadi setiap blok bisa langsung disalin dan dijalankan.
Rollback Journal (Mode DELETE) #
Cara kerja default engine SQLite: sebelum sebuah halaman berkas utama diubah, salinan aslinya disalin dulu ke toko.db-journal. Saat COMMIT, perubahan ditulis ke berkas utama dan journal dihapus — momen journal terhapus itulah titik komit. Bila proses mati sebelum sempat selesai, koneksi berikutnya menemukan journal tersisa dan memulihkan halaman-halaman lama, sehingga transaksi yang belum selesai lenyap tanpa jejak.
Kita bisa menyaksikan siklus itu dari luar. Skrip berikut membuka transaksi dan menahannya dua detik — cukup untuk memotret isi folder di tengah jalan:
rm -f toko.db*
sqlite3 toko.db "CREATE TABLE produk (id INTEGER PRIMARY KEY, nama TEXT NOT NULL, stok INTEGER NOT NULL);
INSERT INTO produk VALUES (1, 'Kopi Gayo 200g', 40);"
ls toko.db*
# transaksi ditahan terbuka selama 2 detik
( echo "BEGIN IMMEDIATE; UPDATE produk SET stok = stok - 1 WHERE id = 1;"; \
sleep 2; echo "COMMIT;" ) | sqlite3 toko.db &
sleep 0.5
ls toko.db*
wait
ls toko.db*Output yang Dihasilkan:
toko.db
toko.db
toko.db-journal
toko.dbUrutan keluarannya: sebelum transaksi hanya ada toko.db; saat transaksi berjalan, toko.db-journal muncul berdampingan dengannya; setelah COMMIT, journal lenyap lagi dan hanya toko.db yang tersisa.
Variasi journal_mode #
PRAGMA journal_mode mengatur bagaimana journal itu diperlakukan. Daftar lengkapnya:
| Mode | Perilaku | Berkas journal setelah COMMIT |
|---|---|---|
DELETE | default; journal dihapus | hilang |
TRUNCATE | journal dipotong kosong | tetap ada, 0 byte |
PERSIST | header journal di-nol-kan | tetap ada, ukuran penuh |
MEMORY | journal disimpan di memori | tidak ada di disk |
WAL | arsitektur append (bahasan berikutnya) | berkas -wal |
OFF | journal dimatikan | tidak ada |
TRUNCATE dan PERSIST menghemat operasi hapus/buat berkas pada sistem operasi yang lambat melakukannya — jurnalnya hanya “dikosongkan”, bukan dihapus. MEMORY mempercepat dengan menaruh journal di RAM. Berikut tampilan mode aktif yang bisa kita cek kapan saja:
sqlite3 toko.db "PRAGMA journal_mode;"Output yang Dihasilkan:
deleteMode WAL #
WAL (Write-Ahead Logging) membalik arsitektur. Alih-alih menimpa berkas utama sambil menyimpan keadaan lama di journal, penulis menambahkan (append) versi halaman baru ke berkas toko.db-wal — berkas utama toko.db tidak disentuh sampai checkpoint. Pembaca cukup menggabungkan berkas utama dan isi WAL untuk melihat data termutakhir.
Pengaktifannya satu baris, dan nilainya menempel permanen pada berkas database:
rm -f toko.db*
sqlite3 toko.db "CREATE TABLE produk (id INTEGER PRIMARY KEY, nama TEXT NOT NULL, stok INTEGER NOT NULL);
INSERT INTO produk VALUES (1, 'Kopi Gayo 200g', 40);
PRAGMA journal_mode=wal;"
# tulis dan COMMIT, koneksi dibiarkan terbuka
( echo "BEGIN; UPDATE produk SET stok = stok - 5 WHERE id = 1;
UPDATE produk SET stok = stok - 5 WHERE id = 1;"; sleep 0.3; echo "COMMIT;"; sleep 2 ) | sqlite3 toko.db &
sleep 1
ls toko.db*
sqlite3 toko.db "PRAGMA wal_checkpoint;"
wait
ls toko.db*
sqlite3 toko.db "SELECT id, nama, stok FROM produk ORDER BY id;"Output yang Dihasilkan:
wal
toko.db
toko.db-shm
toko.db-wal
0|1|1
toko.db
1|Kopi Gayo 200g|30Ada tiga hal yang perlu dibaca dari keluaran tersebut:
PRAGMA journal_mode=wal;mengembalikanwal— konfirmasi bahwa mode baru aktif dan akan tetap aktif untuk koneksi berikutnya, termasuk setelah berkas ditutup dan dibuka lagi.- Saat ada koneksi aktif, muncul
toko.db-waldantoko.db-shm— berkas WAL berisi perubahan yang sudah di-commit, sedangkan berkas shared memory dipakai para koneksi untuk berkoordinasi. Setelah koneksi terakhir menutup database dengan bersih, keduanya dihapus secara otomatis dan hanyatoko.dbyang tersisa. PRAGMA wal_checkpoint;mengembalikan0|1|1— tiga angka itu artinyabusy=0(tidak ada pembaca yang menghalangi),log=1(satu halaman menumpuk di WAL), dancheckpointed=1(satu halaman berhasil disalin kembali ke berkas utama).
Checkpoint adalah proses memindahkan isi WAL kembali ke toko.db. Selain manual lewat PRAGMA wal_checkpoint, engine SQLite menjalankannya otomatis setiap kali WAL menumpuk melewati 1.000 halaman — itulah sebabnya berkas WAL tidak tumbuh tanpa batas pada aplikasi yang terus menulis.
Pembaca dan Penulis Berjalan Bersamaan #
Perubahan terbesar WAL dirasakan pada locking. Pada Chapter Isolation dan Locking kita melihat pembaca yang transaksinya terbuka mampu menggagalkan COMMIT penulis. Di mode WAL, kejadian itu tidak terjadi lagi — keduanya benar-benar berjalan bersamaan:
# Terminal 1 (pembaca): transaksi baca terbuka ~3 detik
( echo "BEGIN; SELECT stok FROM produk WHERE id = 1;"; \
sleep 3; echo "SELECT stok FROM produk WHERE id = 1; COMMIT;" ) | sqlite3 toko.db &
sleep 0.5
# Terminal 2 (penulis): commit saat pembaca masih membaca
sqlite3 toko.db "UPDATE produk SET stok = stok - 2 WHERE id = 1;"
wait
sqlite3 toko.db "SELECT id, nama, stok FROM produk ORDER BY id;"Output yang Dihasilkan:
30
30
1|Kopi Gayo 200g|28Penulis sukses me-commit tanpa galat database is locked padahal pembaca masih membaca. Lebih menarik lagi, SELECT kedua si pembaca masih mengembalikan 30 — pembaca memegang snapshot keadaan saat transaksinya dimulai dan tidak terpengaruh perubahan di sekitarnya. Koneksi baru setelah keduanya selesai melihat hasil akhirnya: stok 28.
Yang tetap berlaku di WAL: hanya satu penulis aktif pada satu waktu. Dua penulis yang berhadapan tetap menghasilkan galat database is locked seperti biasa, sehingga busy_timeout dari chapter sebelumnya tetap relevan.
synchronous FULL dan NORMAL #
PRAGMA synchronous mengatur seberapa sering engine SQLite menjalankan fsync — memaksa data benar-benar ditulis ke media penyimpanan, bukan mengendap di cache sistem operasi. FULL berarti fsync dijalankan pada setiap titik kritis; NORMAL menguranginya dengan menanggung risiko tertentu:
sqlite3 toko.db "PRAGMA synchronous;"
sqlite3 toko.db "PRAGMA journal_mode=wal; PRAGMA synchronous=NORMAL; PRAGMA synchronous;"Output yang Dihasilkan:
2
wal
1Nilai 2 adalah FULL (default), dan 1 adalah NORMAL yang baru saja kita setel. Konsekuensi praktisnya di mode WAL: dengan FULL, setiap COMMIT menunggu fsync berkas WAL selesai; dengan NORMAL, fsync hanya terjadi saat checkpoint. Kehilangan daya listrik di mode NORMAL bisa membuat transaksi-transaksi terakhir menguap — tetapi berkas database tetap utuh dan tidak korup, karena checkpoint berikutnya akan membangun kembali keadaan yang konsisten dari WAL yang tersinkron.
Dengan ini rangkaian transaksi dan konsistensi SQLite lengkap: kita telah membedah BEGIN hingga COMMIT, jaminan ACID di baliknya, perilaku autocommit, pertarungan lock antar koneksi, savepoint, sampai mekanisme di disk yang menjaga semuanya tetap utuh.