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:

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

text
isolation_level bawaan: ''
Setelah INSERT : True
Setelah commit : False

Apa artinya ketiga baris tersebut?

  1. isolation_level bernilai string kosong '': ini mode warisan (legacy) bawaan sqlite3. Module membuka transaction otomatis di belakang perintah DML (INSERT, UPDATE, DELETE), tetapi menjalankan CREATE TABLE di luar transaction — itulah sebabnya in_transaction masih False tepat setelah pembuatan tabel.
  2. Setelah INSERT : True: atribut conn.in_transaction melaporkan ada transaction terbuka yang menunggu nasib — dua baris gudang belum permanen.
  3. 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:

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

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

  1. pindah_stok(conn, 10): dua UPDATE berjalan dalam satu transaction terbuka; stok Pusat sementara 90, Cabang 50 — semuanya masih di memori.
  2. conn.commit(): kedua perubahan diukuhkan bersama. Transkrip membuktikan ('Gudang Pusat', 90), ('Gudang Cabang', 50) tersimpan sah.
  3. pindah_stok_gagal(conn, 10): UPDATE pertama sukses (Pusat menjadi 80), lalu RuntimeError meledak sebelum UPDATE kedua jalan.
  4. except RuntimeError → conn.rollback(): seluruh transaction yang belum di-commit dibatalkan. Stok Pusat dikembalikan ke 90 — perubahan setengah jalan tidak pernah menyentuh database.
  5. 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 INSERT harus tuntas — impor seribu baris yang terputus di tengah meninggalkan data setengah jadi yang sulit diaudit.
  • Konsistensi lintas tabel dipertaruhkan — tabel penjualan dan detail_penjualan dari 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.