Pindahkan 10 karung beras dari Gudang Pusat ke Gudang Cabang butuh dua perintah UPDATE: kurangi stok pusat, tambah stok cabang. Sekarang bayangkan program mati di antara keduanya — listrik padam, jaringan putus, atau exception terjadi. Sepuluh karung menguap: sudah keluar dari Pusat, tapi belum sampai Cabang. Transaction adalah mekanisme database untuk mencegah bencana semacam ini: beberapa perubahan dikemas menjadi satu kesatuan yang berlaku seluruhnya atau tidak sama sekali — jaminan atomicity yang kita janjikan pada Chapter Dasar Database Relasional.
Semantik BEGIN, COMMIT, dan ROLLBACK #
Siklus hidup transaction punya tiga momen kunci. BEGIN menandai titik penyimpanan (savepoint), COMMIT mengukuhkan seluruh perubahan sejak titik tersebut secara permanen, dan ROLLBACK membatalkannya semua seolah tidak pernah terjadi:
Di dalam module sqlite3 kita tidak menulis BEGIN secara manual. Module tersebut membuka transaction otomatis sebelum perintah INSERT, UPDATE, dan DELETE, lalu menyediakan dua method: conn.commit() untuk mengukuhkan dan conn.rollback() untuk membatalkan.
Perilaku Bawaan sqlite3 #
Seberapa sering commit() dibutuhkan? Jawabannya tergantung isolation level — mode yang mengatur kapan transaction dibuka. Mari kita periksa perilaku bawaannya secara langsung:
import sqlite3
conn = sqlite3.connect(":memory:")
print("isolation_level bawaan:", repr(conn.isolation_level))
conn.execute("CREATE TABLE gudang (id INTEGER PRIMARY KEY, nama TEXT, stok INTEGER)")
conn.execute("INSERT INTO gudang VALUES (1, 'Gudang Pusat', 100)")
conn.execute("INSERT INTO gudang VALUES (2, 'Gudang Cabang', 40)")
print("Setelah INSERT :", conn.in_transaction)
conn.commit()
print("Setelah commit :", conn.in_transaction)Output yang Dihasilkan:
isolation_level bawaan: ''
Setelah INSERT : True
Setelah commit : FalseApa artinya ketiga baris tersebut?
isolation_levelbernilai string kosong'': ini mode warisan (legacy) bawaansqlite3. Module membuka transaction otomatis di belakang perintah DML (INSERT,UPDATE,DELETE), tetapi menjalankanCREATE TABLEdi luar transaction — itulah sebabnyain_transactionmasihFalsetepat setelah pembuatan tabel.Setelah INSERT : True: atributconn.in_transactionmelaporkan ada transaction terbuka yang menunggu nasib — dua baris gudang belum permanen.Setelah commit : False:conn.commit()menutup transaction; data kini tertulis kokoh.
Demo Transfer yang Aman #
Sekarang bukti sebenarnya. Masih pada sesi yang sama, kita pindahkan stok lewat fungsi dua langkah, lalu simulasikan kegagalan di tengah jalan:
def pindah_stok(conn, jumlah):
conn.execute("UPDATE gudang SET stok = stok - ? WHERE id = 1", (jumlah,))
conn.execute("UPDATE gudang SET stok = stok + ? WHERE id = 2", (jumlah,))
pindah_stok(conn, 10)
conn.commit()
print("Setelah transfer sah:", conn.execute("SELECT nama, stok FROM gudang").fetchall())
def pindah_stok_gagal(conn, jumlah):
conn.execute("UPDATE gudang SET stok = stok - ? WHERE id = 1", (jumlah,))
raise RuntimeError("koneksi terputus sebelum UPDATE kedua")
try:
pindah_stok_gagal(conn, 10)
except RuntimeError as e:
print("Galat:", e)
conn.rollback()
print("Rollback dijalankan.")
print("Kondisi akhir :", conn.execute("SELECT nama, stok FROM gudang").fetchall())
conn.close()Output yang Dihasilkan:
Setelah transfer sah: [('Gudang Pusat', 90), ('Gudang Cabang', 50)]
Galat: koneksi terputus sebelum UPDATE kedua
Rollback dijalankan.
Kondisi akhir : [('Gudang Pusat', 90), ('Gudang Cabang', 50)]Mari kita telaah apa yang sebenarnya terjadi:
pindah_stok(conn, 10): duaUPDATEberjalan dalam satu transaction terbuka; stok Pusat sementara 90, Cabang 50 — semuanya masih di memori.conn.commit(): kedua perubahan diukuhkan bersama. Transkrip membuktikan('Gudang Pusat', 90), ('Gudang Cabang', 50)tersimpan sah.pindah_stok_gagal(conn, 10):UPDATEpertama sukses (Pusat menjadi 80), laluRuntimeErrormeledak sebelumUPDATEkedua jalan.except RuntimeError→conn.rollback(): seluruh transaction yang belum di-commit dibatalkan. Stok Pusat dikembalikan ke 90 — perubahan setengah jalan tidak pernah menyentuh database.Kondisi akhir: transkrip menunjukkan 90 dan 50, persis seperti setelah commit sah. Tidak ada karung yang menguap.
Tanpa rollback(), transaction yang gagal tetap terbuka dan nasibnya ditentukan kebiasaan penutupan koneksi — bukan tempat bergantung data bisnis. Panggil rollback() secara eksplisit setiap kali exception menginterupsi rangkaian perubahan.
Kapan Transaction Menjadi Wajib #
Tidak semua operasi butuh drama transfer stok. Transaction wajib dipakai ketika:
- Beberapa perubahan membentuk satu makna utuh — transfer antar rekening, pengurangan stok plus pencatatan penjualan, pendaftaran beserta profil.
- Sekumpulan besar
INSERTharus tuntas — impor seribu baris yang terputus di tengah meninggalkan data setengah jadi yang sulit diaudit. - Konsistensi lintas tabel dipertaruhkan — tabel
penjualandandetail_penjualandari Chapter Dasar Database Relasional harus terisi bersama atau tidak sama sekali.
Aturan praktisnya ringkas: kalimat bisnisnya berbunyi “dan” (“kurangi stok dan catat penjualan”), kedua aksi itu satu transaction. Pola try/except dengan commit dan rollback ini juga menjadi fondasi Chapter Connection Management, yang mengatur apa yang terjadi pada koneksi ketika operasi selesai atau gagal.