Foto oleh panumas nikhomkhai via Pexels

Transaction adalah sekumpulan operasi database yang diperlakukan sebagai satu unit kerja tunggal: semua operasi tersebut harus berhasil seluruhnya, atau gagal seluruhnya tanpa ada yang tersisa setengah jalan. Konsep ini dijamin oleh empat prinsip yang dikenal sebagai ACID.

ACID adalah singkatan dari Atomicity (semua-atau-tidak-sama-sekali), Consistency (data selalu berpindah dari satu keadaan valid ke keadaan valid lain), Isolation (transaksi yang berjalan bersamaan tidak saling mengganggu), dan Durability (perubahan yang sudah di-commit akan tetap tersimpan meski terjadi kegagalan sistem sesaat setelahnya).

Memahami ACID bukan sekadar teori, karena ia menjelaskan mengapa BEGIN TRANSACTION, COMMIT, dan ROLLBACK ditulis dengan cara tertentu, dan mengapa kesalahan kecil dalam penanganan transaksi bisa berakibat pada data yang tidak konsisten di dunia nyata.

Atomicity: Semua Berhasil atau Semua Gagal

Transfer saldo sebagai contoh klasik atomicity

BEGIN TRANSACTION; UPDATE Rekening SET Saldo = Saldo - 500000 WHERE RekeningID = 1; UPDATE Rekening SET Saldo = Saldo + 500000 WHERE RekeningID = 2; COMMIT TRANSACTION; -- Expected: jika salah satu UPDATE gagal (misalnya -- karena constraint saldo tidak boleh negatif), TIDAK ADA -- perubahan sama sekali yang tersimpan setelah ROLLBACK
Contoh transfer saldo antar rekening adalah ilustrasi paling umum untuk menjelaskan atomicity: jika pengurangan saldo di rekening pertama berhasil namun penambahan di rekening kedua gagal karena alasan apapun, seluruh transaksi harus dibatalkan sepenuhnya termasuk pengurangan yang sudah terjadi, agar tidak ada uang yang 'hilang' di tengah proses.

Consistency: Menjaga Aturan Data Tetap Valid

CHECK constraint sebagai penjaga consistency

-- Constraint menjaga consistency secara otomatis ALTER TABLE Rekening ADD CONSTRAINT CK_SaldoTidakNegatif CHECK (Saldo >= 0); -- Jika UPDATE membuat saldo menjadi negatif, -- SQL Server akan menolak dan membatalkan transaksi tersebut
Consistency memastikan database selalu berpindah dari satu kondisi yang valid menurut semua aturan (constraint, trigger, foreign key) ke kondisi valid lainnya. Jika sebuah transaksi akan menghasilkan data yang melanggar aturan ini, misalnya saldo menjadi negatif padahal ada CHECK constraint yang melarangnya, transaksi tersebut akan dibatalkan secara otomatis oleh SQL Server.

Isolation: Transaksi Berjalan Tanpa Saling Mengganggu

Isolation mencegah transaksi lain melihat data belum final

-- Sesi A BEGIN TRANSACTION; UPDATE Produk SET Stok = Stok - 1 WHERE ProductID = 10; -- belum COMMIT... -- Sesi B yang berjalan bersamaan tidak akan melihat -- perubahan stok ini sampai Sesi A benar-benar COMMIT, -- tergantung pada isolation level yang dipakai
Isolation mengatur sejauh mana perubahan dari satu transaksi yang belum selesai bisa terlihat oleh transaksi lain yang berjalan bersamaan. Level isolasi yang berbeda (akan dibahas lebih dalam pada artikel terpisah tentang isolation level) memberikan keseimbangan berbeda antara konsistensi data dan performa, karena isolasi yang lebih ketat umumnya berarti lebih banyak locking.

Durability: Data Tersimpan Permanen Setelah Commit

COMMIT menjamin durability lewat transaction log

BEGIN TRANSACTION; INSERT INTO Orders (CustomerID, TotalAmount) VALUES (5, 250000); COMMIT TRANSACTION; -- Setelah baris ini dieksekusi, data dijamin tersimpan -- di transaction log dan akan tetap ada meski -- server tiba-tiba mati sesaat setelahnya
Durability dijamin melalui mekanisme write-ahead logging, di mana SQL Server mencatat perubahan ke transaction log terlebih dahulu sebelum menganggap COMMIT berhasil, sehingga jika terjadi kegagalan sistem tepat setelah COMMIT, data bisa dipulihkan dari log tersebut saat database kembali online, tanpa kehilangan transaksi yang sudah dinyatakan berhasil.

Savepoint dan Nested Transaction di SQL Server

SAVE TRANSACTION memungkinkan rollback sebagian tanpa membatalkan seluruh transaksi

BEGIN TRANSACTION MainTran; UPDATE Accounts SET Balance = Balance - 500000 WHERE AccountID = 1; SAVE TRANSACTION BeforeBonus; UPDATE Accounts SET Balance = Balance + 50000 WHERE AccountID = 1; -- bonus IF EXISTS (SELECT 1 FROM Accounts WHERE AccountID = 1 AND Balance > 10000000) BEGIN ROLLBACK TRANSACTION BeforeBonus; -- hanya batalkan bonus, transfer tetap jalan END COMMIT TRANSACTION MainTran; -- Hasil: pengurangan saldo tetap tersimpan, bonus dibatalkan jika saldo sudah melebihi batas

SAVE TRANSACTION membuat titik tengah (savepoint) di dalam sebuah transaksi yang sedang berjalan, sehingga ROLLBACK TRANSACTION bisa diarahkan ke nama savepoint tersebut untuk membatalkan hanya perubahan yang terjadi setelah savepoint dibuat, tanpa membatalkan keseluruhan transaksi dari awal. Fitur ini berguna ketika sebagian logika di tengah transaksi bersifat opsional atau bergantung pada kondisi yang baru diketahui belakangan.

SQL Server sebenarnya tidak memiliki konsep true nested transaction seperti beberapa RDBMS lain; memanggil BEGIN TRANSACTION berulang kali hanya menaikkan nilai @@TRANCOUNT, dan ROLLBACK TRANSACTION tanpa nama akan selalu membatalkan seluruh rangkaian transaksi hingga level terluar, bukan hanya level BEGIN TRANSACTION terakhir, sehingga pola yang terlihat seperti transaksi bertingkat pada kode aplikasi sebenarnya tetap diatomic sebagai satu transaksi tunggal.

Memahami perbedaan ini penting agar developer tidak salah berasumsi bahwa ROLLBACK di dalam sub-procedure hanya akan membatalkan perubahan procedure tersebut; karena nyatanya rollback tanpa nama savepoint akan membatalkan seluruh transaksi induk, termasuk perubahan yang sudah dilakukan oleh pemanggil sebelum procedure tersebut dipanggil.

Pertumbuhan Transaction Log Akibat Transaksi Panjang

Transaksi yang tidak kunjung commit menahan virtual log file agar tidak bisa dipakai ulang

-- Transaksi panjang yang membiarkan log tidak bisa di-truncate sampai COMMIT BEGIN TRANSACTION; UPDATE LargeTable SET Status = 'Processed' WHERE BatchID = 1; -- 5 juta baris -- ...proses lain yang memakan waktu lama tanpa COMMIT... COMMIT TRANSACTION; -- Selama transaksi ini terbuka, seluruh log record-nya tidak bisa dibebaskan -- Memeriksa apa yang menahan log agar tidak bisa di-truncate/shrink SELECT name, log_reuse_wait_desc FROM sys.databases WHERE name = 'SalesDB'; -- Contoh hasil: log_reuse_wait_desc = 'ACTIVE_TRANSACTION' -- Memantau transaksi aktif tertua yang sedang menahan log DBCC OPENTRAN('SalesDB'); -- Menunjukkan SPID, waktu mulai transaksi, dan teks transaksi yang belum selesai

Setiap operasi yang mengubah data dicatat ke transaction log sebelum perubahan diterapkan ke data file, dan log record tersebut baru bisa ditandai bisa dipakai ulang (truncate) setelah transaksi yang menghasilkannya selesai di-commit dan, pada recovery model FULL, setelah BACKUP LOG dijalankan. Transaksi yang berjalan sangat lama, misalnya memproses jutaan baris dalam satu BEGIN TRANSACTION tunggal, akan menahan seluruh log record sejak transaksi dimulai agar tidak bisa dipakai ulang.

Dampak paling umum dari situasi ini adalah file transaction log bertumbuh sangat cepat karena ruang log lama tidak bisa dipakai ulang selama transaksi masih terbuka, bahkan jika BACKUP LOG dijadwalkan rutin sekalipun, karena backup log tidak bisa membebaskan ruang yang masih dipakai transaksi aktif. Pada kasus ekstrem, log file bisa memenuhi seluruh disk dan menyebabkan database berhenti menerima transaksi baru.

Mitigasi umum adalah memecah operasi besar menjadi batch-batch kecil dengan commit terpisah di setiap batch, misalnya memproses 10000 baris lalu COMMIT sebelum melanjutkan ke batch berikutnya, sehingga ruang log bisa dipakai ulang secara bertahap. DBCC OPENTRAN dan kolom log_reuse_wait_desc pada sys.databases menjadi alat diagnosis utama untuk mengidentifikasi transaksi mana yang menjadi penyebab log tidak kunjung mengecil, sehingga tindakan perbaikan bisa diarahkan ke sesi yang tepat tanpa harus menebak penyebabnya.

Transaksi Terdistribusi Lintas Server dengan MSDTC

BEGIN DISTRIBUTED TRANSACTION dikoordinasikan oleh MSDTC lintas dua server

-- Transaksi yang melibatkan dua server berbeda melalui linked server BEGIN DISTRIBUTED TRANSACTION; UPDATE ServerA.SalesDB.dbo.Accounts SET Balance = Balance - 500000 WHERE AccountID = 1; UPDATE ServerB.InventoryDB.dbo.StockLedger SET Quantity = Quantity - 10 WHERE ProductID = 200; COMMIT TRANSACTION; -- Kedua UPDATE pada server berbeda akan commit bersamaan atau rollback bersamaan, -- dikoordinasikan oleh Microsoft Distributed Transaction Coordinator (MSDTC)

Transaksi terdistribusi diperlukan ketika satu unit kerja logis harus mengubah data pada lebih dari satu instance SQL Server sekaligus, misalnya melalui linked server, dan seluruh perubahan tersebut harus bersifat atomic yaitu semuanya berhasil commit atau semuanya rollback bersamaan. BEGIN DISTRIBUTED TRANSACTION secara otomatis melibatkan MSDTC begitu pernyataan menyentuh server kedua.

MSDTC berperan sebagai koordinator yang menjalankan protokol two-phase commit, di mana pada fase pertama seluruh server yang terlibat diminta memastikan kesiapan commit, dan hanya setelah semua menyatakan siap barulah fase commit sesungguhnya dijalankan serentak. Layanan MSDTC harus aktif dan dikonfigurasi mengizinkan komunikasi jaringan antar server, karena transaksi akan gagal total jika MSDTC pada salah satu server tidak dapat dihubungi. Transaksi terdistribusi umumnya memiliki overhead dan latensi lebih tinggi dibanding transaksi lokal biasa, sehingga pendekatan ini sebaiknya dipertimbangkan hanya ketika konsistensi atomic lintas server benar-benar diperlukan, bukan sekadar kemudahan arsitektur.

Kesalahan Umum yang Sering Terjadi

Membuka Transaksi Tapi Tidak Pernah Menutupnya

Transaksi yang dibuka dengan BEGIN TRANSACTION namun tidak diakhiri COMMIT atau ROLLBACK karena ada exception yang tidak tertangani akan membuat lock tertahan lama dan memblokir sesi lain yang mencoba mengakses data yang sama.

Mengira Semua Error Otomatis Membatalkan Transaksi

Tidak semua error di SQL Server otomatis melakukan rollback transaksi; beberapa error hanya menghentikan statement yang sedang berjalan sementara transaksi tetap terbuka, sehingga developer harus eksplisit memeriksa ini di dalam TRY-CATCH.

Membuat Transaksi Terlalu Panjang dan Lama

Transaksi yang mencakup terlalu banyak operasi atau menunggu input dari luar (seperti panggilan API eksternal) di tengah transaksi akan menahan lock terlalu lama, meningkatkan risiko deadlock dan menurunkan concurrency secara keseluruhan.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Menjalankan operasi dalam transaksi eksplisit memberi jaminan konsistensi data yang kuat, namun menambah overhead locking dan logging dibanding menjalankan statement individual tanpa transaksi, sehingga transaksi sebaiknya dibuat seringkas mungkin.

Untuk operasi baca-saja yang tidak mengubah data, transaksi eksplisit umumnya tidak diperlukan karena SQL Server secara default sudah membungkus setiap statement tunggal dalam transaksi implisit; transaksi eksplisit paling dibutuhkan ketika beberapa statement harus berhasil atau gagal bersama-sama.

Ringkasan

ACID adalah fondasi yang menjelaskan mengapa transaksi database dirancang seperti sekarang: Atomicity menjamin semua-atau-tidak-sama-sekali, Consistency menjaga aturan data, Isolation mencegah gangguan antar transaksi, dan Durability memastikan data yang sudah commit tidak hilang. Memahami keempatnya membantu menulis kode transaksi yang benar-benar aman, bukan sekadar menambahkan BEGIN TRANSACTION tanpa memahami konsekuensinya.

Seri Belajar SQL Server:

← Sebelumnya: Trigger di SQL Server dan Kapan Digunakan

→ Selanjutnya: Isolation Level dan Locking di SQL Server