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 #

JaminanYang dijanjikanMekanisme di baliknya
Atomicityrangkaian perubahan berjalan semua atau tidak sama sekalirollback journal memulihkan kondisi awal
Consistencydata tidak pernah melanggar aturan (constraint)pemeriksaan PK, FK, CHECK saat transaksi
Isolationtransaksi bersamaan tidak saling melihat hasil setengah jadikunci baca/tulis dan snapshot
Durabilityperubahan yang di-COMMIT bertahan meski sistem matipenulisan 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:

python
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:

text
-- 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:

  1. koneksi.isolation_level = None: menonaktifkan transaksi implisit modul sqlite3; batas transaksi ditentukan murni oleh BEGIN kita.
  2. UPDATE pertama: saldo Budi berkurang menjadi 350.000 dan terlihat oleh koneksi penulis.
  3. raise RuntimeError(...): kegagalan terjadi sebelum kredit ke Siti — meniru dependensi eksternal yang mati.
  4. ROLLBACK: seluruh perubahan sejak BEGIN dibatalkan — 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:

python
try:
    koneksi.execute(
        "UPDATE dompet SET saldo = saldo - 999999999 WHERE pemilik = 'Budi'"
    )
except sqlite3.IntegrityError as galat:
    print("Consistency menjaga:", galat)

Output yang Dihasilkan:

text
Consistency menjaga: CHECK constraint failed: saldo >= 0

Transfer 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:

python
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:

text
-- 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.