Memindahkan saldo antar rekening membutuhkan dua UPDATE: saldo pengirim dikurangi, saldo penerima ditambah. Jika proses terputus di antara keduanya, uang hilang begitu saja atau berlipat ganda. Transaksi memecahkan masalah ini dengan mengelompokkan beberapa statement menjadi satu unit kerja: seluruh perubahan terjadi bersama-sama, atau tidak ada sama sekali.
Skema Contoh #
Sepanjang chapter ini kita memakai tabel rekening berikut di berkas toko.db:
CREATE TABLE rekening (
id INTEGER PRIMARY KEY,
pemilik TEXT NOT NULL,
saldo INTEGER NOT NULL CHECK (saldo >= 0)
);
INSERT INTO rekening VALUES (1, 'Budi', 500000), (2, 'Siti', 200000);Batasan CHECK (saldo >= 0) akan kita manfaatkan untuk memicu galat secara sengaja di bagian akhir.
Menjalankan Transaksi: BEGIN dan COMMIT #
Contoh klasiknya adalah transfer Rp150.000 dari Budi ke Siti:
BEGIN;
UPDATE rekening SET saldo = saldo - 150000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 150000 WHERE id = 2;
COMMIT;
SELECT id, pemilik, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
1|Budi|350000
2|Siti|350000Mari kita bedah pernyataan-pernyataan penting di atas:
- Baris 1 (
BEGIN): membuka transaksi dan mematikan perilaku autocommit untuk koneksi ini. Semua statement berikutnya dianggap bagian dari satu unit kerja yang sama. - Baris 2-3 (dua
UPDATE): engine SQLite menjalankan keduanya, tetapi hasilnya masih bersifat sementara — koneksi lain belum dapat melihatnya. - Baris 4 (
COMMIT): menjadikan seluruh perubahan permanen. Titik ini adalah momen “semua atau tidak sama sekali” terjadi.
Membatalkan dengan ROLLBACK #
Sebelum COMMIT, kita bisa membuang seluruh perubahan dengan ROLLBACK. Buka transaksi dan kurangi saldo Budi, lalu sengaja jangan commit:
BEGIN;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;Sekarang buka terminal kedua dan baca tabel yang sama lewat koneksi baru:
sqlite3 toko.db "SELECT id, pemilik, saldo FROM rekening ORDER BY id;"Output yang Dihasilkan:
1|Budi|350000
2|Siti|350000Koneksi kedua masih melihat saldo lama karena perubahan di koneksi pertama belum di-commit. Namun koneksi yang sama dengan transaksi terbuka bisa melihat keadaan sementaranya sendiri:
SELECT id, pemilik, saldo FROM rekening ORDER BY id;
ROLLBACK;Output yang Dihasilkan:
1|Budi|250000
2|Siti|350000SELECT di atas dijalankan di dalam transaksi, sehingga saldo Budi tampak sudah berkurang menjadi 250000. ROLLBACK kemudian membuang seluruh perubahan tersebut, dan keadaan kembali seperti semula:
SELECT id, pemilik, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
1|Budi|350000
2|Siti|350000Statement Gagal, Transaksi Tetap Terbuka #
Apa yang terjadi jika salah satu statement di tengah transaksi gagal? Mari kita uji dengan menarik saldo jauh melebihi isi rekening:
BEGIN;
UPDATE rekening SET saldo = saldo - 10000 WHERE id = 1;
UPDATE rekening SET saldo = saldo - 999999 WHERE id = 1;Output yang Dihasilkan:
Runtime error: CHECK constraint failed: saldo >= 0 (19)Statement kedua ditolak karena 340000 dikurangi 999999 menghasilkan saldo negatif. Yang penting diamati: galat ini tidak menutup transaksi. Statement yang gagal dibatalkan sendiri oleh SQLite, tetapi statement pertama tetap berlaku dan transaksi masih terbuka:
SELECT id, pemilik, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
1|Budi|340000
2|Siti|350000Karena transaksi masih terbuka, keputusan ada di tangan kita — di sini kita memilih membuang semuanya:
ROLLBACK;
SELECT id, pemilik, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
1|Budi|350000
2|Siti|350000DDL pun Bisa Di-rollback #
Berbeda dari MySQL yang melakukan implicit commit setiap kali DDL dijalankan, SQLite memperlakukan CREATE TABLE, DROP TABLE, dan ALTER TABLE sebagai statement biasa di dalam transaksi:
BEGIN;
CREATE TABLE mutasi (
id INTEGER PRIMARY KEY,
rekening_id INTEGER NOT NULL,
keterangan TEXT NOT NULL,
jumlah INTEGER NOT NULL
);
INSERT INTO mutasi VALUES (1, 1, 'transfer keluar', -150000);
SELECT COUNT(*) FROM mutasi;
ROLLBACK;Output yang Dihasilkan:
1Di dalam transaksi, tabel mutasi ada dan berisi satu baris. Setelah ROLLBACK, tabelnya ikut lenyap:
sqlite3 toko.db ".tables"Output yang Dihasilkan:
rekeningTransaksi adalah fondasi seluruh jaminan konsistensi di SQLite. Bagaimana engine benar-benar menjamin bahwa unit kerja ini utuh saat listrik mati atau proses dibunuh? Itu bahasan Chapter Memahami ACID berikutnya.