Selama ini kita menulis INSERT dan UPDATE tanpa BEGIN, dan semuanya langsung tersimpan begitu statement selesai. Perilaku ini bernama autocommit: mode bawaan SQLite di mana setiap statement adalah transaksinya sendiri. Chapter ini membedah cara kerja mode tersebut, cara mengambil alih kendali secara manual, dan mengapa membiarkan ribuan komit kecil berjalan bisa sangat lambat.

Setiap Statement Adalah Transaksi Sendiri #

Tanpa BEGIN yang terbuka, engine SQLite membungkus setiap statement dengan pasangan BEGIN dan COMMIT implisit. Tabel contoh kita untuk chapter ini adalah produk:

sql
CREATE TABLE produk (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL,
  harga INTEGER NOT NULL
);

Dua perintah berikut dijalankan dari terminal sebagai koneksi terpisah — dan setiap koneksi menyelesaikan satu transaksi penuh:

bash
sqlite3 toko.db "INSERT INTO produk (nama, harga) VALUES ('Kopi Gayo 200g', 45000);"
sqlite3 toko.db "INSERT INTO produk (nama, harga) VALUES ('Gula Aren 500g', 30000);"
sqlite3 toko.db "SELECT id, nama, harga FROM produk ORDER BY id;"

Output yang Dihasilkan:

text
1|Kopi Gayo 200g|45000
2|Gula Aren 500g|30000

Setelah masing-masing INSERT selesai, tidak ada jendela waktu untuk membatalkan — perubahan sudah permanen. Bahkan SELECT pun berjalan sebagai transaksi baca singkat: kunci dibuka, data dibaca, kunci dilepas.

Beralih ke Kontrol Manual #

BEGIN memindahkan koneksi ke mode manual: autocommit mati hingga COMMIT atau ROLLBACK datang. Mari kita buka transaksi dan sisipkan satu produk lagi:

sql
BEGIN;
INSERT INTO produk (nama, harga) VALUES ('Teh Melati 25 sachet', 15000);

Dari terminal kedua, baris ketiga ini belum terlihat:

bash
sqlite3 toko.db "SELECT COUNT(*) FROM produk;"

Output yang Dihasilkan:

text
2

Kembali ke koneksi pertama dan akhiri transaksinya:

sql
COMMIT;
bash
sqlite3 toko.db "SELECT COUNT(*) FROM produk;"

Output yang Dihasilkan:

text
3

Sebagai catatan sintaksis, END adalah sinonim resmi COMMIT — keduanya identik secara perilaku:

sql
BEGIN;
INSERT INTO produk (nama, harga) VALUES ('Keripik Singkong 100g', 12000);
END;

SELECT COUNT(*) FROM produk;

Output yang Dihasilkan:

text
4

Mendeteksi Status Transaksi #

sqlite3 CLI tidak menyediakan PRAGMA untuk memeriksa apakah sebuah koneksi sedang berada di dalam transaksi — kita cukup melacak BEGIN yang pernah diketik. Di sisi aplikasi, modul sqlite3 milik Python menyediakan atribut in_transaction:

python
import sqlite3

con = sqlite3.connect(":memory:", isolation_level=None)
con.execute("CREATE TABLE t(x)")
print(con.in_transaction)
con.execute("BEGIN")
con.execute("INSERT INTO t VALUES (1)")
print(con.in_transaction)
con.execute("COMMIT")
print(con.in_transaction)

Output yang Dihasilkan:

text
False
True
False

Atribut in_transaction berubah menjadi True sejak BEGIN hingga COMMIT atau ROLLBACK. Parameter isolation_level=None membuat Python berperilaku persis seperti CLI — tanpa parameter ini, modul sqlite3 diam-diam membungkus setiap INSERT, UPDATE, dan DELETE dengan transaksinya sendiri.

Harga Sebuah Komit #

Setiap komit di mode rollback journal melakukan pekerjaan fisik yang nyata: berkas journal dibuat, isi lama disalin, keduanya di-fsync ke media penyimpanan, lalu journal dihapus. Mengulang siklus ini 1.000 kali berarti 1.000 kali kerja tersebut. Perbandingannya mudah diukur:

bash
SQL=$(for i in $(seq 1 1000); do echo "INSERT INTO stok (nama, harga) VALUES ('Produk $i', $((1000+i)));"; done)
rm -f waktu.db
time sqlite3 waktu.db "CREATE TABLE stok (id INTEGER PRIMARY KEY, nama TEXT NOT NULL, harga INTEGER NOT NULL); $SQL"
rm -f waktu.db
time sqlite3 waktu.db "CREATE TABLE stok (id INTEGER PRIMARY KEY, nama TEXT NOT NULL, harga INTEGER NOT NULL); BEGIN; $SQL COMMIT;"
rm -f waktu.db

Kedua perintah menyisipkan 1.000 baris yang sama persis; bedanya hanya bungkus BEGIN ... COMMIT. Di mesin penulis, versi autocommit memakan waktu sekitar 33 milidetik, sedangkan versi satu transaksi selesai dalam 3 milidetik — sekitar sepuluh kali lebih cepat. Pada harddisk yang lebih lambat dalam menangani fsync, rasionya kerap mencapai puluhan hingga ratusan kali.

Satu konsekuensi lain dari autocommit: dua proses yang sama-sama menulis akan saling berhadapan dengan galat database is locked. Bagaimana SQLite mengatur antrean penulis dan pembaca ini dibahas pada Chapter Isolation dan Locking.