Tiga chapter terakhir memberi Anda alat: BEGIN, COMMIT, ROLLBACK, dan SAVEPOINT. Chapter ini menjawab pertanyaan yang lebih mendasar: janji apa sebenarnya yang PostgreSQL berikan lewat alat-alat itu? Jawabannya dirangkum dalam akronim ACID — Atomicity, Consistency, Isolation, dan Durability — empat jaminan yang bekerja bersama supaya database layak dipercaya menyimpan uang orang. Setiap huruf menjawab satu skenario kegagalan: transaksi setengah jalan, data yang melanggar aturan, dua pengguna menulis bersamaan, dan server yang mati listrik. Kita bedah keempatnya satu per satu dan membuktikan masing-masing dengan demo yang bisa Anda jalankan sendiri di tabel rekening.
Empat Jaminan dalam Satu Akronim #
Sebelum masuk ke demo, mari petakan dulu keempat jaminan ke pertanyaan dan mekanisme yang menopangnya di PostgreSQL:
| Jaminan | Pertanyaan yang dijawab | Mekanisme di PostgreSQL |
|---|---|---|
| Atomicity | Bagaimana jika transaksi gagal di tengah jalan? | Seluruh perubahan diterapkan bersamaan atau tidak sama sekali (COMMIT/ROLLBACK) |
| Consistency | Bagaimana jika perubahan melanggar aturan data? | Constraint (CHECK, FOREIGN KEY, UNIQUE) dan tipe data diperiksa sebelum commit diterima |
| Isolation | Bagaimana jika dua transaksi berjalan bersamaan? | Snapshot dan MVCC: setiap transaksi bekerja seolah sendirian, sesuai isolation level |
| Durability | Bagaimana jika server mati tepat setelah commit? | Write-Ahead Log (WAL): catatan perubahan ditulis dan di-flush ke disk sebelum commit dijawab sukses |
Baris-baris tabel ini bukan teori kosong — keempatnya akan kita buktikan dengan query sungguhan di bagian-bagian berikutnya, semuanya di atas satu tabel yang sama.
Menyiapkan Tabel Latihan #
Tabel rekening yang sama dari chapter-chapter sebelumnya dipakai lagi, lengkap dengan dua aturan bisnisnya: saldo tidak boleh negatif, dan saldo e-wallet dibatasi maksimal Rp1.000.000. Nilai sum(saldo) di akhir file akan menjadi alat ukur favorit kita untuk membuktikan konsistensi:
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;
SELECT sum(saldo) AS total_saldo FROM rekening;Output yang Dihasilkan:
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)
total_saldo
-------------
1750000.00
(1 row)Tiga rekening dengan total saldo Rp1.750.000 siap diuji. Simpan angka total ini baik-baik — angka inilah yang harus tetap sama setiap kali kita memindahkan uang antar-rekening.
Atomicity: Semua atau Tidak Sama Sekali #
Atomicity menjamin transaksi adalah unit yang tidak terpecah. Bukti paling tajam adalah transaksi yang gagal di tengah jalan: jika satu langkah saja gagal, tidak ada satu pun langkah yang boleh tersimpan. Kita jalankan transfer Budi ke Dewi yang sudah dua kali gagal karena batas e-wallet — dan kali ini fokus kita adalah kondisi tabel setelah kegagalan itu:
BEGIN;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 3;Output yang Dihasilkan:
psql:atomicity.sql:5: ERROR: new row for relation "rekening" violates check constraint "saldo_ewallet_maks"
DETAIL: Failing row contains (3, Dewi Lestari, e-wallet, 1050000.00).UPDATE pertama berhasil memotong saldo Budi, UPDATE kedua ditolak constraint, dan psql berhenti di titik itu dengan transaksi masih terbuka. Pertanyaan atomicity: apakah potongan Rp100.000 tadi tersimpan? Pemeriksaan tabel menjawab:
SELECT id, nama, jenis, saldo FROM rekening ORDER BY id;
SELECT sum(saldo) AS total_saldo FROM rekening;Output yang Dihasilkan:
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)
total_saldo
-------------
1750000.00
(1 row)Saldo Budi tetap Rp500.000 dan total tetap Rp1.750.000 — tidak ada yang berubah walau perintah pertama sempat “berhasil”. Saat psql keluar dengan transaksi terbuka, PostgreSQL menjalankan ROLLBACK implisit; hasil akhirnya identik dengan ROLLBACK eksplisit yang sudah Anda lihat di chapter Konsep Transaksi
. Inilah atomicity bekerja: satu kegagalan di titik mana pun membatalkan seluruh unit kerja. Savepoint dari chapter sebelumnya tidak melanggar jaminan ini — ROLLBACK TO SAVEPOINT tetap menghasilkan akhir transaksi yang bersih: semua efek yang tersisa setelah commit adalah rangkaian valid.
Consistency: Data Selalu dalam Aturan #
Consistency menjamin transaksi membawa data dari satu state valid ke state valid lainnya. Dua lapis penjaganya berbeda peran. Lapis pertama adalah constraint: CHECK, FOREIGN KEY, dan UNIQUE menolak perubahan yang melanggar aturan — penolakan saldo_ewallet_maks yang berulang kali kita pakai adalah bukti lapis ini; tanpa atomicity, penolakan constraint justru menjadi pintu masuk state rusak seperti uang yang menguap di chapter pertama.
Lapis kedua adalah invarian bisnis yang melintasi beberapa baris — misalnya “uang tidak diciptakan atau dimusnahkan oleh transfer”. Transfer yang sah harus mempertahankan sum(saldo):
SELECT sum(saldo) AS total_sebelum FROM rekening;
BEGIN;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;
COMMIT;
SELECT sum(saldo) AS total_sesudah FROM rekening;
SELECT id, nama, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
total_sebelum
---------------
1750000.00
(1 row)
BEGIN
UPDATE 1
UPDATE 1
COMMIT
total_sesudah
---------------
1750000.00
(1 row)
id | nama | saldo
----+--------------+-----------
1 | Budi Santoso | 400000.00
2 | Siti Aminah | 400000.00
3 | Dewi Lestari | 950000.00
(3 rows)Total sebelum dan sesudah sama-sama Rp1.750.000, sementara SELECT terakhir menunjukkan uang benar-benar berpindah: Budi turun ke Rp400.000, Siti naik ke Rp400.000. Invarian terjaga, dan state akhir tetap memenuhi semua constraint — itulah transaksi konsisten.
Isolation: Transaksi Tidak Saling Intip #
Isolation menjamin transaksi bersamaan tidak saling mengganggu pandangan data. Standar SQL mendefinisikan beberapa tingkat isolasi, dan PostgreSQL menetapkan read committed sebagai default. Query berikut memperlihatkan level saat ini dan cara menaikkannya menjadi serializable untuk satu transaksi saja:
SHOW TRANSACTION ISOLATION LEVEL;
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SHOW TRANSACTION ISOLATION LEVEL;
COMMIT;
SHOW TRANSACTION ISOLATION LEVEL;Output yang Dihasilkan:
transaction_isolation
-----------------------
read committed
(1 row)
BEGIN
SET
transaction_isolation
-----------------------
serializable
(1 row)
COMMIT
transaction_isolation
-----------------------
read committed
(1 row)SHOW di tengah transaksi melaporkan serializable, lalu nilai kembali ke read committed setelah COMMIT — pengaturan level berlaku per transaksi, bukan permanen. Membuktikan efek isolasi yang sesungguhnya membutuhkan dua sesi yang berjalan bersamaan: satu sesi membaca, satu sesi lain mengubah saldo dan commit, lalu sesi pertama membaca lagi — hasilnya berbeda antara level read committed dan repeatable read. Eksperimen dua terminal itu dibahas tuntas di chapter Isolation Levels
, dan mesin snapshot yang membuatnya bekerja dibedah di chapter MVCC
.
Durability: Bertahan Melewati Mati Listrik #
Durability menjanjikan hasil COMMIT bertahan melewati crash server. Kuncinya adalah Write-Ahead Log (WAL): sebelum menjawab “commit berhasil”, PostgreSQL menulis catatan perubahan ke log di disk dan memastikan catatan itu benar-benar sampai ke media penyimpanan — menulis dulu, menjawab belakangan. Dua parameter berikut membuktikan mesin itu aktif di server Anda:
SHOW wal_level;
SHOW synchronous_commit;Output yang Dihasilkan:
wal_level
-----------
replica
(1 row)
synchronous_commit
--------------------
on
(1 row)wal_level = replica berarti WAL aktif dan lengkap (nama levelnya menunjukkan WAL ini juga bahan bakar replication), dan synchronous_commit = on berarti setiap commit menunggu WAL sampai ke disk dulu. Alur lengkapnya terlihat seperti ini:
Jika server mati sebelum flush selesai, transaksi dianggap tidak pernah terjadi — konsisten dengan atomicity. Jika mati setelahnya, proses crash recovery saat server start ulang memutar ulang WAL dan memulihkan semua transaksi yang sudah di-commit. WAL juga fondasi seluruh strategi backup dan Point-in-Time Recovery yang dibahas di section Backup dan Recovery , khususnya chapter Write-Ahead Logging di sana.
Empat jaminan ini tidak berdiri sendiri-sendiri: atomicity menutup celah setengah-transaksi, consistency menjaga aturan, isolation meredam tabrakan konkurensi, dan durability mengunci hasilnya melampaui bencana. Dari keempatnya, isolation adalah yang paling sering mengejutkan pengembang aplikasi — karena efeknya baru terasa ketika dua transaksi benar-benar berjalan bersamaan. Chapter berikutnya masuk ke situ: membedah Read Committed, Repeatable Read, dan Serializable beserta fenomena aneh yang masing-masing level izinkan atau cegah.
Referensi #
- Transactions (Tutorial 3.4)
- Constraints (DDL)
- Transaction Isolation
- Reliability and the Write-Ahead Log
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
atomicity-cek.sql
Unduh berkasSELECT id, nama, jenis, saldo FROM rekening ORDER BY id;
SELECT sum(saldo) AS total_saldo FROM rekening;
atomicity.sql
Unduh berkasBEGIN;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 3;
buat-tabel.sql
Unduh berkasCREATE 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;
SELECT sum(saldo) AS total_saldo FROM rekening;
durability.sql
Unduh berkasSHOW wal_level;
SHOW synchronous_commit;
isolation-preview.sql
Unduh berkasSHOW TRANSACTION ISOLATION LEVEL;
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SHOW TRANSACTION ISOLATION LEVEL;
COMMIT;
SHOW TRANSACTION ISOLATION LEVEL;
konsistensi.sql
Unduh berkasSELECT sum(saldo) AS total_sebelum FROM rekening;
BEGIN;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;
COMMIT;
SELECT sum(saldo) AS total_sesudah FROM rekening;
SELECT id, nama, saldo FROM rekening ORDER BY id;