Chapter sebelumnya memperkenalkan transaksi sebagai pembatas unit kerja all-or-nothing. Sekarang kita pegang kemudinya secara penuh: BEGIN membuka transaksi, COMMIT mengunci seluruh perubahan menjadi permanen, dan ROLLBACK membuang semuanya seolah tidak pernah terjadi. Ketiga perintah ini adalah vocabulary inti dari semua pemrograman database — aplikasi Anda, driver ORM, sampai tool backup semuanya bertumpu pada siklus yang sama. Pada chapter ini kita jalankan siklus itu tuntas di tabel rekening, lalu bedah satu per satu perilaku kurang terkenal yang sering mengejutkan pemula: apa yang terjadi setelah error di tengah transaksi, dan bagaimana autocommit bekerja di balik layar.

Menyiapkan Tabel Latihan #

Kita pakai kembali tabel rekening dengan aturan bisnis yang sama: saldo tidak boleh negatif, dan saldo e-wallet dibatasi maksimal Rp1.000.000. Batas kedua inilah yang akan kita manfaatkan untuk memicu error secara terkendali di bagian akhir chapter:

sql buat-tabel.sql
CREATE TABLE rekening (
    id    integer PRIMARY KEY,
    nama  text NOT NULL,
    jenis text NOT NULL,
    saldo numeric(12, 2) NOT NULL CHECK (saldo >= 0),
    CONSTRAINT saldo_ewallet_maks
        CHECK (jenis <> 'e-wallet' OR saldo <= 1000000)
);

INSERT INTO rekening (id, nama, jenis, saldo) VALUES
    (1, 'Budi Santoso', 'tabungan', 500000),
    (2, 'Siti Aminah', 'tabungan', 300000),
    (3, 'Dewi Lestari', 'e-wallet', 950000);

SELECT id, nama, jenis, saldo FROM rekening ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 3
 id |     nama     |  jenis   |   saldo   
----+--------------+----------+-----------
  1 | Budi Santoso | tabungan | 500000.00
  2 | Siti Aminah  | tabungan | 300000.00
  3 | Dewi Lestari | e-wallet | 950000.00
(3 rows)

Tiga rekening siap: Budi Rp500.000, Siti Rp300.000, dan Dewi Rp950.000 di e-wallet yang tinggal Rp50.000 lagi sebelum mentok di batas atas.

Siklus Hidup Sebuah Transaksi #

Sebelum masuk ke perintah, mari petakan seluruh perjalanan state-nya. Sebuah sesi PostgreSQL berada dalam salah satu dari tiga keadaan: autocommit (tidak ada transaksi terbuka), di dalam transaksi aktif, atau di dalam transaksi yang gagal (aborted) setelah error:

Diagram ini memperlihatkan dua fakta penting. Pertama, dari state Aktif hanya ada dua pintu keluar — COMMIT atau ROLLBACK — tidak ada jalan “setengah jalan”. Kedua, state Gagal tidak bisa kembali ke Aktif: begitu satu perintah di dalam transaksi mengalami error, seluruh transaksi ditandai batal dan pintu COMMIT yang tersisa pun hanya berperan sebagai rollback. Perilaku kedua ini akan kita buktikan dengan demo sungguhan di bagian Error di Tengah Transaksi.

COMMIT: Mengunci Perubahan Menjadi Permanen #

Transfer Rp100.000 dari Budi ke Siti adalah unit kerja dua langkah. Kita bungkus dengan BEGIN, jalankan kedua UPDATE, lihat kondisi di tengah jalan, lalu tutup dengan COMMIT:

BEGIN;

UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;

UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;

SELECT id, nama, saldo FROM rekening WHERE id IN (1, 2) ORDER BY id;

COMMIT;

SELECT id, nama, saldo FROM rekening WHERE id IN (1, 2) ORDER BY id;

Output yang Dihasilkan:

text
BEGIN
UPDATE 1
UPDATE 1
 id |     nama     |   saldo   
----+--------------+-----------
  1 | Budi Santoso | 400000.00
  2 | Siti Aminah  | 400000.00
(2 rows)

COMMIT
 id |     nama     |   saldo   
----+--------------+-----------
  1 | Budi Santoso | 400000.00
  2 | Siti Aminah  | 400000.00
(2 rows)

Mari kita bedah siklusnya tahap demi tahap:

  1. BEGIN memindahkan sesi dari mode autocommit ke mode transaksi aktif. Tag BEGIN di output menandai transaksi sudah terbuka.
  2. Dua UPDATE dieksekusi berurutan: saldo Budi turun ke Rp400.000, saldo Siti naik ke Rp400.000. Keduanya masih berstatus sementara.
  3. SELECT pertama di dalam transaksi memperlihatkan hasil kedua perubahan — Rp400.000 dan Rp400.000. Sesi pembuat transaksi selalu melihat perubahannya sendiri, meskipun belum di-commit.
  4. COMMIT menjadikan seluruh perubahan permanen dan terlihat oleh semua sesi lain mulai saat itu juga.
  5. SELECT kedua setelah COMMIT menampilkan angka yang sama — Rp400.000 dan Rp400.000. Perubahan bertahan melampaui akhir transaksi; inilah yang membedakan commit dari sekadar tampilan sementara.

ROLLBACK: Membatalkan Seluruh Perubahan #

ROLLBACK adalah kebalikan COMMIT: seluruh efek transaksi dibuang dan data kembali ke kondisi sebelum BEGIN. Skenarionya: Siti ingin menarik tunai Rp50.000, aplikasi sudah memotong saldonya, tetapi kemudian mesin ATM gagal — transaksi dibatalkan:

BEGIN;

UPDATE rekening SET saldo = saldo - 50000 WHERE id = 2;

SELECT id, nama, saldo FROM rekening WHERE id = 2;

ROLLBACK;

SELECT id, nama, saldo FROM rekening WHERE id = 2;

Output yang Dihasilkan:

text
BEGIN
UPDATE 1
 id |    nama     |   saldo   
----+-------------+-----------
  2 | Siti Aminah | 350000.00
(1 row)

ROLLBACK
 id |    nama     |   saldo   
----+-------------+-----------
  2 | Siti Aminah | 400000.00
(1 row)

Dua SELECT yang mengapit ROLLBACK memperlihatkan kontras intinya: di dalam transaksi saldo Siti sempat terbaca Rp350.000, tetapi setelah ROLLBACK saldonya kembali menjadi Rp400.000. Tag ROLLBACK menegaskan seluruh transaksi — termasuk UPDATE yang tadi dilaporkan sukses dengan UPDATE 1 — telah dibatalkan. Perintah sukses di dalam transaksi yang diakhiri ROLLBACK tidak meninggalkan jejak apa pun.

Error di Tengah Transaksi #

Sekarang skenario yang paling sering membingungkan pemula: error terjadi di tengah transaksi. Kita coba transfer Rp100.000 dari Budi ke Dewi — penambahan ke e-wallet Dewi akan ditolak constraint batas saldo. Demo ini dijalankan sebagai sesi psql interaktif agar kita bisa melihat apa yang terjadi pada perintah-perintah setelah error:

psql -X 2>&1 <<'SQL'
BEGIN;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 3;
SELECT saldo FROM rekening WHERE id = 1;
ROLLBACK;
SELECT saldo FROM rekening WHERE id = 1;
SQL

Output yang Dihasilkan:

text
BEGIN
UPDATE 1
ERROR:  new row for relation "rekening" violates check constraint "saldo_ewallet_maks"
DETAIL:  Failing row contains (3, Dewi Lestari, e-wallet, 1050000.00).
ERROR:  current transaction is aborted, commands ignored until end of transaction block
ROLLBACK
   saldo   
-----------
 400000.00
(1 row)

Urutan peristiwanya menegaskan diagram siklus hidup di awal chapter:

  1. BEGIN dan UPDATE pertama berjalan normal — saldo Budi terpotong Rp100.000 di dalam transaksi.
  2. UPDATE kedua ditolak constraint saldo_ewallet_maks: Rp1.050.000 melebihi batas e-wallet. Sejak titik ini transaksi berstatus aborted.
  3. SELECT berikutnya ditolak juga — bukan karena salah, tetapi karena pesan current transaction is aborted, commands ignored until end of transaction block: transaksi sudah gagal, PostgreSQL menolak semua perintah baru sampai transaksi diakhiri. Perhatikan bahwa bahkan perintah baca pun ikut ditolak.
  4. ROLLBACK satu-satunya perintah yang masih diterima, mengakhiri transaksi gagal itu dan membuang seluruh perubahannya.
  5. SELECT terakhir bekerja normal kembali dan menunjukkan saldo Budi tetap Rp400.000 — pengurangan di langkah pertama ikut dibatalkan.

Ada satu penyelamat diam-diam pada state aborted ini: jika klien terputus — aplikasi crash, jaringan mati, atau psql ditutup paksa — PostgreSQL otomatis menjalankan ROLLBACK implisit atas transaksi yang masih terbuka. Kegagalan di tengah jalan tidak pernah meninggalkan setengah transaksi menggantung di server.

Autocommit dan Transaksi Implisit #

Tanpa BEGIN, setiap perintah berjalan dalam transaksi implisitnya sendiri: UPDATE tunggal langsung di-commit begitu sukses — inilah mode autocommit yang kita buktikan di chapter sebelumnya. Behavior ini bisa diubah dari sisi klien. psql menyediakan variabel AUTOCOMMIT:

text
\set AUTOCOMMIT off

Dengan \set AUTOCOMMIT off, psql membuka transaksi implisit sebelum perintah tulis pertama, dan Anda bertanggung jawab menutupnya dengan COMMIT atau ROLLBACK — mirip perilaku default MySQL yang memakai mode ini. Kebanyakan driver aplikasi (JDBC, psycopg, node-postgres) juga punya pengaturan sejenis, dan ORM seperti Hibernate aktif memakai transaksi programatik. Kunci transaksi tetap sama di semua lapisan: kelompok perintah dibuka dengan BEGIN dan ditutup dengan COMMIT/ROLLBACK.

Dua perilaku kecil lainnya patut diketahui agar tidak mengejutkan Anda di kemudian hari. BEGIN di dalam transaksi yang sedang berjalan tidak error — PostgreSQL hanya mengeluarkan WARNING: there is already a transaction in progress dan transaksi lama tetap berlanjut. Sebaliknya, COMMIT atau ROLLBACK saat tidak ada transaksi terbuka juga hanya menghasilkan WARNING: there is no transaction in progress, bukan error.

BEGIN TRANSACTION dan Opsi Tambahan #

BEGIN menerima dua sinonim opsional — BEGIN WORK dan BEGIN TRANSACTION — yang berfungsi identik demi kepatuhan standar SQL. Yang lebih menarik: BEGIN juga menerima transaction mode yang mengatur sifat transaksi sejak awal. Contoh yang paling sering dipakai adalah isolation level:

BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

SHOW TRANSACTION ISOLATION LEVEL;

COMMIT;

SHOW TRANSACTION ISOLATION LEVEL;

Output yang Dihasilkan:

text
BEGIN
 transaction_isolation 
-----------------------
 repeatable read
(1 row)

COMMIT
 transaction_isolation 
-----------------------
 read committed
(1 row)

SHOW TRANSACTION ISOLATION LEVEL di dalam transaksi melaporkan repeatable read, sementara setelah COMMIT nilai kembali ke read committed — default seluruh cluster PostgreSQL. Opsi ini hanya pratinjau: isolation level menentukan seberapa terisolasi transaksi Anda dari transaksi lain yang berjalan bersamaan, topik yang dibahas tuntas di chapter Isolation Levels.

Tiga perintah ini memberi Anda kendali penuh atas batas transaksi. Namun keduanya hitam-putih: COMMIT menerapkan semuanya, ROLLBACK membuang semuanya. Bagaimana jika satu transaksi berisi banyak langkah dan Anda hanya ingin membatalkan sebagian — misalnya membuang satu catatan yang gagal tanpa mengulang seluruh transfer dari awal? Untuk itu PostgreSQL menyediakan SAVEPOINT, chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

buat-tabel.sql

Unduh berkas
sql
CREATE TABLE rekening (
    id    integer PRIMARY KEY,
    nama  text NOT NULL,
    jenis text NOT NULL,
    saldo numeric(12, 2) NOT NULL CHECK (saldo >= 0),
    CONSTRAINT saldo_ewallet_maks
        CHECK (jenis <> 'e-wallet' OR saldo <= 1000000)
);

INSERT INTO rekening (id, nama, jenis, saldo) VALUES
    (1, 'Budi Santoso', 'tabungan', 500000),
    (2, 'Siti Aminah', 'tabungan', 300000),
    (3, 'Dewi Lestari', 'e-wallet', 950000);

SELECT id, nama, jenis, saldo FROM rekening ORDER BY id;

error-membatalkan.sh

Unduh berkas
bash
psql -X 2>&1 <<'SQL'
BEGIN;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 3;
SELECT saldo FROM rekening WHERE id = 1;
ROLLBACK;
SELECT saldo FROM rekening WHERE id = 1;
SQL

preview-isolation.sql

Unduh berkas
sql
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;

SHOW TRANSACTION ISOLATION LEVEL;

COMMIT;

SHOW TRANSACTION ISOLATION LEVEL;

siklus-commit.sql

Unduh berkas
sql
BEGIN;

UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;

UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;

SELECT id, nama, saldo FROM rekening WHERE id IN (1, 2) ORDER BY id;

COMMIT;

SELECT id, nama, saldo FROM rekening WHERE id IN (1, 2) ORDER BY id;

siklus-rollback.sql

Unduh berkas
sql
BEGIN;

UPDATE rekening SET saldo = saldo - 50000 WHERE id = 2;

SELECT id, nama, saldo FROM rekening WHERE id = 2;

ROLLBACK;

SELECT id, nama, saldo FROM rekening WHERE id = 2;