Section Dasar SQL sudah memperkenalkan BEGIN, COMMIT, dan ROLLBACK lewat atomicity. Padahal atomicity hanya satu dari empat jaminan database modern ketika banyak proses menulis dan membaca data yang sama. Kerangka lengkapnya bernama ACID — Atomicity, Consistency, Isolation, Durability — dengan tiap huruf menjawab mode kegagalan berbeda: setengah perubahan, data melanggar aturan, pembaca melihat hasil setengah jadi, serta perubahan yang raib saat komputer mati. Chapter ini menguji keempatnya lewat skenario klasik data engineering: transfer saldo antar dompet pelanggan toko online.
Empat Jaminan ACID #
| Jaminan | Yang dijanjikan | Mekanisme di baliknya |
|---|---|---|
| Atomicity | rangkaian perubahan berjalan semua atau tidak sama sekali | rollback journal memulihkan kondisi awal |
| Consistency | data tidak pernah melanggar aturan (constraint) | pemeriksaan PK, FK, CHECK saat transaksi |
| Isolation | transaksi bersamaan tidak saling melihat hasil setengah jadi | kunci baca/tulis dan snapshot |
| Durability | perubahan yang di-COMMIT bertahan meski sistem mati | penulisan journal/WAL ke disk sebelum commit diakui |
Kita uji tiga jaminan pertama secara nyata; durability dijelaskan di bagian akhir karena membuktikannya menuntut mematikan mesin di tengah commit.
Atomicity: Transfer yang Gagal di Tengah Jalan #
Skenarionya: Budi mentransfer Rp150.000 ke Siti. Kedua UPDATE — saldo Budi berkurang, saldo Siti bertambah — harus berjalan sebagai satu kesatuan; kegagalan sengaja dipicu setelah UPDATE pertama berhasil. Karena bagian akhir chapter memakai dua koneksi, contoh memakai file dompet.db, bukan memori:
import sqlite3
koneksi = sqlite3.connect("dompet.db")
koneksi.executescript("""
CREATE TABLE dompet (
pemilik TEXT PRIMARY KEY,
saldo INTEGER NOT NULL CHECK (saldo >= 0)
);
INSERT INTO dompet VALUES
('Budi', 500000),
('Siti', 200000);
""")
koneksi.commit()
koneksi.isolation_level = None # transaksi manual: hanya BEGIN yang membuka transaksi
def tampilkan_saldo(db, judul):
print(f"-- {judul} --")
for baris in db.execute("SELECT pemilik, saldo FROM dompet ORDER BY pemilik"):
print(baris)
tampilkan_saldo(koneksi, "saldo awal")
koneksi.execute("BEGIN")
try:
koneksi.execute("UPDATE dompet SET saldo = saldo - 150000 WHERE pemilik = 'Budi'")
layanan_notifikasi_aktif = False # simulasi: layanan eksternal mati
if not layanan_notifikasi_aktif:
raise RuntimeError("layanan notifikasi tidak tersedia")
koneksi.execute("UPDATE dompet SET saldo = saldo + 150000 WHERE pemilik = 'Siti'")
koneksi.execute("COMMIT")
except RuntimeError as galat:
koneksi.execute("ROLLBACK")
print("transaksi dibatalkan:", galat)
tampilkan_saldo(koneksi, "saldo setelah rollback")Output yang Dihasilkan:
-- saldo awal --
('Budi', 500000)
('Siti', 200000)
transaksi dibatalkan: layanan notifikasi tidak tersedia
-- saldo setelah rollback --
('Budi', 500000)
('Siti', 200000)Mari kita telaah alur transaksi di atas:
koneksi.isolation_level = None: menonaktifkan transaksi implisit modulsqlite3; batas transaksi ditentukan murni olehBEGINkita.UPDATEpertama: saldo Budi berkurang menjadi 350.000 dan terlihat oleh koneksi penulis.raise RuntimeError(...): kegagalan terjadi sebelum kredit ke Siti — meniru dependensi eksternal yang mati.ROLLBACK: seluruh perubahan sejakBEGINdibatalkan — saldo Budi kembali ke 500.000. Tidak ada kondisi “uang sudah keluar tetapi belum sampai”.
Consistency: Constraint sebagai Penjaga #
Consistency memastikan transaksi membawa database dari satu kondisi sah ke kondisi sah berikutnya — “sah” didefinisikan constraint. Batasan CHECK (saldo >= 0) diuji langsung:
try:
koneksi.execute(
"UPDATE dompet SET saldo = saldo - 999999999 WHERE pemilik = 'Budi'"
)
except sqlite3.IntegrityError as galat:
print("Consistency menjaga:", galat)Output yang Dihasilkan:
Consistency menjaga: CHECK constraint failed: saldo >= 0Transfer yang mencoba menguras saldo melewati batas nol ditolak sebelum tersimpan. Constraint inilah yang membuat definisi “data sah” hidup di dalam database, bukan hanya di kepala engineer — pipeline yang keliru pun gagal dengan galat yang jelas.
Isolation: Mencegah Dirty Read #
Dirty read terjadi bila pembaca melihat perubahan yang belum di-COMMIT — data yang mungkin tidak pernah ada bila transaksinya berakhir ROLLBACK. SQLite mencegahnya; kita buktikan dengan dua koneksi terpisah pada file yang sama, transfer kali ini berjalan tuntas:
koneksi.execute("BEGIN")
koneksi.execute("UPDATE dompet SET saldo = saldo - 150000 WHERE pemilik = 'Budi'")
koneksi.execute("UPDATE dompet SET saldo = saldo + 150000 WHERE pemilik = 'Siti'")
pembaca = sqlite3.connect("dompet.db")
tampilkan_saldo(pembaca, "pembaca lain: transfer belum dikomit")
tampilkan_saldo(koneksi, "penulis: perubahan terlihat sendiri")
koneksi.execute("COMMIT")
tampilkan_saldo(pembaca, "pembaca lain: setelah COMMIT")Output yang Dihasilkan:
-- pembaca lain: transfer belum dikomit --
('Budi', 500000)
('Siti', 200000)
-- penulis: perubahan terlihat sendiri --
('Budi', 350000)
('Siti', 350000)
-- pembaca lain: setelah COMMIT --
('Budi', 350000)
('Siti', 350000)Perbandingannya tegas: selama transaksi terbuka, penulis melihat saldo baru (350.000) sementara pembaca masih melihat saldo lama (500.000). Barulah setelah COMMIT, pembaca melihat hasil akhir — dirty read tidak pernah terjadi. SQLite mewujudkannya dengan mengunci file: penulis memegang kunci tulis, pembaca membaca kondisi terakhir yang sah. PostgreSQL bahkan menerima READ UNCOMMITTED namun menjalankannya sebagai READ COMMITTED; rincian tingkatannya adalah materi chapter Isolation Levels.
Durability: Commit yang Bertahan #
Durability menjanjikan: begitu COMMIT menjawab “berhasil”, perubahan sudah tertulis permanen, bukan hanya di memori. SQLite mewujudkannya dengan menuliskan rollback journal (atau write-ahead log pada mode WAL) ke media penyimpanan sebelum commit diakui. Mati listrik sesaat setelah commit meninggalkan database pada kondisi setelah transaksi; mati sebelum journal tuntas memulihkan kondisi sebelum transaksi. Kedua kondisi sama-sama sah — kondisi setengah jalan tidak pernah dihasilkan. Prinsip serupa berlaku pada PostgreSQL; rujukan resminya: isolation di SQLite
dan perintah transaksi SQLite
.
Empat jaminan ACID menjadikan database layak dipercaya untuk data bisnis. Namun isolation punya tingkatan — makin ketat jaminannya, makin besar dampaknya pada konkurensi. Bagaimana read committed, repeatable read, dan serializable bekerja serta anomali apa yang lolos di tiap tingkatnya adalah pokok bahasan chapter Isolation Levels.