Membuka transaksi hanya setengah dari pekerjaan; dua penutupnya menentukan nasib seluruh rangkaian. COMMIT mengukuhkan semua perubahan menjadi permanen, sedangkan ROLLBACK membatalkannya semua seolah rangkaian tidak pernah terjadi. Di antara keduanya terselip aturan main yang jarang disadali pemula: koneksi lain tidak melihat perubahan sebelum COMMIT, dan kegagalan satu statement di tengah transaksi tidak otomatis membatalkan transaksi. Pada chapter ini kita uji keempat perilaku itu dengan transfer uang sungguhan — lengkap dengan catatan mutasinya — lalu menutup dengan preview pembatalan parsial ROLLBACK TO.
Menyiapkan Tabel Latihan #
Skema yang sama dengan chapter sebelumnya menemani kita: tabel rekening menyimpan saldo, tabel mutasi mencatat setiap pergerakan uang dan terikat foreign key ke rekening:
DROP DATABASE IF EXISTS belajar_trx;
CREATE DATABASE belajar_trx;
USE belajar_trx;
CREATE TABLE rekening (
id INT AUTO_INCREMENT PRIMARY KEY,
pemilik VARCHAR(50) NOT NULL,
saldo DECIMAL(12,2) NOT NULL
);
CREATE TABLE mutasi (
id INT AUTO_INCREMENT PRIMARY KEY,
rekening_id INT NOT NULL,
jumlah DECIMAL(12,2) NOT NULL,
keterangan VARCHAR(100) NOT NULL,
tanggal DATE NOT NULL,
CONSTRAINT fk_mutasi_rekening FOREIGN KEY (rekening_id) REFERENCES rekening(id)
);
INSERT INTO rekening (pemilik, saldo) VALUES ('Budi', 500000.00), ('Siti', 300000.00);
SELECT id, pemilik, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changed
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
+----+---------+-----------+
| id | pemilik | saldo |
+----+---------+-----------+
| 1 | Budi | 500000.00 |
| 2 | Siti | 300000.00 |
+----+---------+-----------+
2 rows in set (0.00 sec)COMMIT: Mengukuhkan Perubahan #
Transfer Rp100.000,00 dari Budi kepada Siti kita jalankan penuh di dalam transaksi, lengkap dengan dua catatan mutasi, lalu kita tutup dengan COMMIT:
START TRANSACTION;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;
INSERT INTO mutasi (rekening_id, jumlah, keterangan, tanggal) VALUES
(1, -100000.00, 'Transfer ke Siti', '2026-01-15'),
(2, 100000.00, 'Transfer dari Budi', '2026-01-15');
SELECT id, pemilik, saldo FROM rekening ORDER BY id;
COMMIT;
SELECT id, pemilik, saldo FROM rekening ORDER BY id;
SELECT id, rekening_id, jumlah, keterangan FROM mutasi ORDER BY id;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
+----+---------+-----------+
| id | pemilik | saldo |
+----+---------+-----------+
| 1 | Budi | 400000.00 |
| 2 | Siti | 400000.00 |
+----+---------+-----------+
2 rows in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+----+---------+-----------+
| id | pemilik | saldo |
+----+---------+-----------+
| 1 | Budi | 400000.00 |
| 2 | Siti | 400000.00 |
+----+---------+-----------+
2 rows in set (0.00 sec)
+----+-------------+------------+--------------------+
| id | rekening_id | jumlah | keterangan |
+----+-------------+------------+--------------------+
| 1 | 1 | -100000.00 | Transfer ke Siti |
| 2 | 2 | 100000.00 | Transfer dari Budi |
+----+-------------+------------+--------------------+
2 rows in set (0.00 sec)Empat statement DML berjalan sebagai satu unit kerja: SELECT di dalam transaksi dan SELECT setelah COMMIT menampilkan keadaan yang sama — saldo Budi Rp400.000,00, saldo Siti Rp400.000,00, dan dua baris mutasi tercatat. Titik COMMIT adalah titik tidak-kembali: setelah statement itu berhasil, ROLLBACK yang dijalankan belakangan tidak lagi bisa membatalkan apa pun karena transaksinya sudah tertutup. Sejak saat ini, hanya UPDATE atau DELETE baru yang bisa mengubah data — itulah makna “permanen” pada COMMIT.
ROLLBACK: Membatalkan Perubahan #
Sekarang kita coba cabang yang berlawanan. Skenarionya: aplikasi hendak menambah bonus Rp250.000,00 ke rekening Budi, tetapi di tengah proses validasi bisnis memutuskan operasi dibatalkan:
START TRANSACTION;
UPDATE rekening SET saldo = saldo + 250000 WHERE id = 1;
SELECT saldo FROM rekening WHERE id = 1;
ROLLBACK;
SELECT saldo FROM rekening WHERE id = 1;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
+-----------+
| saldo |
+-----------+
| 650000.00 |
+-----------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+-----------+
| saldo |
+-----------+
| 400000.00 |
+-----------+
1 row in set (0.00 sec)SELECT pertama masih menampilkan saldo Rp650.000,00 karena koneksi kita sendiri selalu melihat perubahan yang belum di-commit. ROLLBACK kemudian memutar ulang seluruh perubahan — SELECT kedua menampilkan saldo Rp400.000,00, tepat nilai sebelum transaksi dibuka. InnoDB menyelesaikan pembatalan ini lewat undo log: setiap perubahan di dalam transaksi disimpan versi lamanya, sehingga ROLLBACK cukup memutar balik catatan demi catatan tanpa membongkar seluruh database.
Visibilitas Antar-Koneksi: Baru Terlihat Setelah COMMIT #
Aturan penting berikutnya tidak bisa dibuktikan dari satu terminal: koneksi lain tidak melihat perubahan yang belum di-commit. Blok-blok kode pada buku ini berjalan sebagai koneksi terpisah, sehingga kita bisa meniru dua aplikasi yang berbeda. Koneksi pertama membuka transaksi dan mengubah saldo tanpa menutupnya:
START TRANSACTION;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 1;
SELECT saldo FROM rekening WHERE id = 1;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
+-----------+
| saldo |
+-----------+
| 500000.00 |
+-----------+
1 row in set (0.00 sec)Koneksi pertama melihat saldo Rp500.000,00 — Rp400.000,00 ditambah bonus Rp100.000,00 yang belum di-commit. Ketika koneksi tersebut terputus tanpa menutup transaksinya, server membatalkan transaksi terbuka secara otomatis. Koneksi kedua yang datang kemudian membaca keadaan sebenarnya yang ter-commit:
SELECT saldo FROM rekening WHERE id = 1;Output yang Dihasilkan:
+-----------+
| saldo |
+-----------+
| 400000.00 |
+-----------+
1 row in set (0.00 sec)Saldo terbaca Rp400.000,00: bonus Rp100.000,00 tidak pernah terlihat oleh koneksi lain. Pada sistem produksi dengan dua terminal sungguhan, pola yang sama berlaku selama koneksi pertama masih hidup — pembaca tetap tidak melihat perubahan sebelum COMMIT dijalankan. Kini kita buktikan sisi sebaliknya: perubahan yang sudah di-commit langsung terlihat oleh koneksi mana pun:
START TRANSACTION;
UPDATE rekening SET saldo = saldo - 75000 WHERE id = 1;
COMMIT;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 0 rows affected (0.00 sec)Koneksi baru membaca hasilnya:
SELECT saldo FROM rekening WHERE id = 1;Output yang Dihasilkan:
+-----------+
| saldo |
+-----------+
| 325000.00 |
+-----------+
1 row in set (0.00 sec)Saldo kini Rp325.000,00 — pengurangan Rp75.000,00 yang barusan di-commit langsung terlihat oleh koneksi lain. Dua arah aturan ini menyatu menjadi satu kalimat: di dalam transaksi kita melihat perubahan sendiri, di luar transaksi orang hanya melihat hasil COMMIT.
Error di Tengah Transaksi Tidak Membatalkannya #
Pemula sering mengira transaksi otomatis batal ketika salah satu statementnya gagal. Kenyataannya di MySQL, error pada satu statement hanya menghentikan statement itu — transaksi tetap terbuka, dan perubahan statement sebelumnya tetap menunggu keputusan kita. Kita buktikan dengan menyisipkan INSERT yang melanggar primary key di tengah transaksi:
-- @error
START TRANSACTION;
UPDATE rekening SET saldo = saldo + 75000 WHERE id = 1;
INSERT INTO mutasi (id, rekening_id, jumlah, keterangan, tanggal) VALUES (5, 1, 75000.00, 'Bonus', '2026-01-15');
INSERT INTO mutasi (id, rekening_id, jumlah, keterangan, tanggal) VALUES (5, 2, 25000.00, 'Uji ganda', '2026-01-16');
SELECT saldo FROM rekening WHERE id = 1;
ROLLBACK;
SELECT saldo FROM rekening WHERE id = 1;
SELECT COUNT(*) AS jumlah_mutasi FROM mutasi;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 1 row affected (0.00 sec)
ERROR 1062 (23000): Duplicate entry '5' for key 'mutasi.PRIMARY'
+-----------+
| saldo |
+-----------+
| 400000.00 |
+-----------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
+-----------+
| saldo |
+-----------+
| 325000.00 |
+-----------+
1 row in set (0.00 sec)
+---------------+
| jumlah_mutasi |
+---------------+
| 2 |
+---------------+
1 row in set (0.00 sec)Mari kita bedah urutannya: UPDATE menaikkan saldo Budi Rp75.000,00 di dalam transaksi, INSERT pertama menambah mutasi ber-id 5, lalu INSERT kedua gagal dengan ERROR 1062 karena id 5 sudah dipakai baris sebelumnya. Meski error, transaksi tidak batal — SELECT setelahnya masih menampilkan saldo Rp400.000,00 (Rp325.000,00 plus bonus yang belum di-commit). Baru ROLLBACK yang kita tulis sendiri membatalkan seluruh rangkaian: saldo kembali Rp325.000,00 dan COUNT(*) menunjukkan tabel mutasi tetap berisi dua baris — INSERT pertama ikut terpulihkan. Tanggung jawab menutup transaksi sepenuhnya ada di tangan aplikasi, bukan di tangan server.
Preview ROLLBACK TO: Membatalkan Sebagian #
ROLLBACK yang kita pakai sejauh ini bersifat total — seluruh transaksi dibatalkan. MySQL juga menyediakan pembatalan parsial lewat savepoint: penanda bernama di tengah transaksi yang bisa menjadi titik pulih. Perintah ROLLBACK TO SAVEPOINT nama membatalkan hanya statement setelah penanda tersebut, sementara ROLLBACK tanpa nama membatalkan semuanya. Penanda itu kita buat dengan SAVEPOINT nama dan bisa dilepas dengan RELEASE SAVEPOINT nama — ketiganya dibedah tuntas di chapter berikutnya.
Membersihkan Database Latihan #
Rangkaian latihan selesai dan database latihan kita jatuhkan kembali:
DROP DATABASE belajar_trx;Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)Referensi #
- START TRANSACTION, COMMIT, and ROLLBACK Statements — MySQL 8.4 Reference Manual
- The InnoDB Transaction Model — MySQL 8.4 Reference Manual
COMMIT dan ROLLBACK kini teruji dari dua sisi: kekuatan mengukuhkan dan membatalkan, visibilitas yang baru terbuka bagi koneksi lain setelah COMMIT, plus fakta bahwa error di tengah jalan tidak membatalkan transaksi secara ajaib. Masalah berikutnya berlapis: bagaimana bila transaksi panjang perlu dibatalkan hanya sebagian — misalnya proses pendaftaran lima langkah yang gagal di langkah keempat? SAVEPOINT menjawabnya dengan titik pulih bernama di tengah transaksi, materi chapter berikutnya.