Belajar SQL PostgreSQL - Transaction Management, ACID & Concurrency Control
Episode 11 of 21

Belajar SQL PostgreSQL - Transaction Management, ACID & Concurrency Control

Episode ini membahas prinsip ACID, perintah transaksi BEGIN COMMIT ROLLBACK dan SAVEPOINT, isolation level di PostgreSQL, serta locking eksplisit dengan SELECT FOR UPDATE dan FOR SHARE untuk mencegah race condition dan double spending.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Pendahuluan

Selamat datang di episode 11 series Belajar SQL PostgreSQL! Sejauh ini kita belajar mengolah data — tapi ada satu pertanyaan besar yang belum kita jawab: apa yang terjadi kalau dua user mengubah data yang sama secara bersamaan? Bayangkan dua orang membeli produk yang stoknya tinggal satu, dua transaksi menarik uang dari saldo yang sama, atau dua order memakai voucher yang sama. Tanpa perlindungan yang tepat, semua skenario ini bisa menghasilkan data yang salah dan hilangnya uang sungguhan.

Jawaban atas semua masalah ini adalah transaction — mekanisme yang menjamin sekelompok operasi database berjalan sebagai satu kesatuan yang tak terpisahkan. Dan di balik transaction, ada empat pilar yang disebut ACID. Memahami keduanya adalah batas antara aplikasi yang "kelihatan jalan" dengan aplikasi yang benar-benar aman.

Di episode ini, kita akan membahas prinsip ACID, perintah transaksi BEGIN, COMMIT, ROLLBACK, dan SAVEPOINT, fenomena concurrency dan isolation level di PostgreSQL, serta locking eksplisit dengan SELECT ... FOR UPDATE untuk mencegah race condition.

Prinsip ACID pada Database

ACID adalah akronim empat properti yang menjamin keandalan transaksi database:

PropertiArtiAnalogi
AtomicityTransaksi all-or-nothing: semua statement sukses, atau semuanya batalTransfer uang: uang keluar dan masuk, atau tidak sama sekali
ConsistencyDatabase berpindah dari satu state valid ke state valid lainConstraint selalu terjaga setelah transaksi
IsolationTransaksi berjalan seolah-olah sendirianDua kasir tidak saling melihat setengah pekerjaan
DurabilityPerubahan yang sudah commit tetap ada walau server crashBukti transaksi tidak hilang setelah mati listrik

Perintah Transaksi

BEGIN, COMMIT, dan ROLLBACK

Ketiga perintah ini adalah siklus hidup transaksi:

  • BEGIN: memulai transaksi.
  • COMMIT: menyimpan semua perubahan secara permanen.
  • ROLLBACK: membatalkan semua perubahan sejak BEGIN.
Transaksi lengkap
BEGIN;
 
INSERT INTO orders (customer_id, total)
VALUES ('1c2d3e4f-0000-0000-0000-000000000001', 150000);
 
UPDATE inventory
SET stock = stock - 1
WHERE product_id = '9a8b7c6d-0000-0000-0000-000000000001';
 
COMMIT;

Jika salah satu statement di atas gagal — katakanlah stok tidak cukup dan CHECK (stock >= 0) menolak — kalian bisa memilih ROLLBACK untuk mengembalikan semuanya, termasuk insert order yang sudah jalan. Atomicity dijamin.

Tip

Satu rahasia yang jarang diketahui pemula: setiap statement di psql sudah berjalan dalam transaksi otomatis yang langsung di-commit (autocommit). Jadi BEGIN hanyalah cara menyatakan "mulai sekarang, saya yang pegang kendali sampai saya bilang COMMIT atau ROLLBACK". Kebiasaan menulis transaksi eksplisit untuk operasi multi-step adalah penanda developer berpengalaman.

SAVEPOINT: Titik Pemulihan Parsial

Kadang kita tidak ingin membatalkan seluruh transaksi — hanya sebagian. SAVEPOINT membuat titik pemulihan parsial:

SAVEPOINT untuk rollback parsial
BEGIN;
 
INSERT INTO orders (customer_id, total) VALUES (1, 50000);
 
SAVEPOINT order_created;
 
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (10, 20, 1);
 
ROLLBACK TO order_created;
UPDATE order_items SET quantity = 2 WHERE order_id = 10;
 
COMMIT;

Setelah ROLLBACK TO order_created, hanya perubahan sejak savepoint tersebut yang dibatalkan — insert order tetap dipertahankan. Ini berguna untuk alur bisnis yang bercabang dengan fallback.

Transaction Isolation Levels & Concurrency Phenomena

Isolation membahas: seberapa "terisolasi" transaksi satu dari yang lain. Tanpa isolasi yang cukup, muncul fenomena concurrency yang mendistorsi hasil.

Fenomena Concurrency

FenomenaDeskripsiContoh
Dirty ReadMembaca data transaksi lain yang belum commitMembaca saldo yang belum final
Non-Repeatable ReadData yang sama berubah di antara dua pembacaanBaca harga, harga berubah sebelum dibaca ulang
Phantom ReadKumpulan baris hasil query berubah di antara dua eksekusiBaca daftar produk, ada produk baru muncul
Serialization AnomalyHasil transaksi bersamaan tidak seperti eksekusi berurutanOverbooking tiket karena dua transaksi "menang"

Isolation Level di PostgreSQL

PostgreSQL punya tiga isolation level (PostgreSQL tidak pernah mengalami dirty read karena MVCC):

LevelMencegahMasih memungkinkan
Read Committed (default)Dirty ReadNon-Repeatable Read, Phantom Read
Repeatable ReadDirty, Non-Repeatable ReadPhantom Read (terkadang), Serialization Anomaly
SerializableSemua fenomena— (gagal dengan error serialization bila konflik)

Read Committed adalah default PostgreSQL: setiap statement melihat snapshot terbaru yang sudah commit. Untuk transaksi yang panjang dan butuh pandangan konsisten, gunakan Repeatable Read. Serializable dipakai saat akurasi ketat lebih penting daripada throughput.

Set isolation level
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT balance FROM accounts WHERE id = 1;
COMMIT;

Note

MVCC (Multi-Version Concurrency Control) adalah alasan PostgreSQL bisa punya isolasi kuat tanpa mengorbankan concurrency: setiap transaksi melihat "snapshot" versi data pada saat ia mulai, sehingga pembaca tidak pernah memblokir penulis dan penulis tidak pernah memblokir pembaca. Inilah warisan yang disebut-sebut di episode 1.

Explicit Locking: SELECT ... FOR UPDATE

Isolation level melindungi dari fenomena baca, tapi tidak mencegah write-write race. Bayangkan dua transaksi sama-sama membaca stok 5, keduanya mengurangi 1, dan keduanya menulis 4 — padahal seharusnya jadi 3. Ini disebut race condition, dan inilah asal dari bencana double spending dan overbooking.

Solusinya adalah locking eksplisit: kunci baris agar transaksi lain menunggu.

FOR UPDATE: Kunci untuk Mengubah Data

SELECT ... FOR UPDATE mengunci baris yang dibaca sampai transaksi selesai. Transaksi lain yang mencoba mengunci atau mengubah baris yang sama akan menunggu:

Pessimistic locking untuk stock
BEGIN;
 
SELECT stock
FROM inventory
WHERE product_id = '9a8b7c6d-0000-0000-0000-000000000001'
FOR UPDATE;
 
UPDATE inventory
SET stock = stock - 1
WHERE product_id = '9a8b7c6d-0000-0000-0000-000000000001';
 
COMMIT;

Dengan FOR UPDATE, transaksi kedua yang memproses order untuk produk yang sama akan menunggu sampai transaksi pertama commit. Race condition double spending berhasil dicegah.

FOR SHARE: Kunci Berbagi untuk Membaca

SELECT ... FOR SHARE mengunci baris agar tidak bisa diubah oleh transaksi lain, tapi masih bisa dibaca (bahkan di-lock share oleh banyak transaksi). Cocok saat beberapa proses ingin memastikan data tidak berubah selama memproses:

FOR SHARE untuk baca konsisten
BEGIN;
SELECT id, price FROM products WHERE id = 5 FOR SHARE;
COMMIT;

Warning

FOR UPDATE adalah keputusan desain — bukan default. Terlalu banyak lock menurunkan concurrency; terlalu sedikit menyebabkan race condition. Kunci transaksi harus sesingkat mungkin: lakukan di dalam transaction kecil yang langsung COMMIT, jangan memegang lock sambil menunggu input user atau panggilan API lambat — kalau tidak, barisan antrian menunggu akan menggunung.

Isolasi Sisi Pembaca: Row Lock vs Blokir Baca

Perlu ditegaskan: dengan MVCC, baris yang di-lock FOR UPDATE masih bisa dibaca oleh transaksi lain (mereka melihat versi snapshot lama). Yang diblokir hanya pembaruan — transaksi lain harus menunggu sebelum mengunci/mengubah baris tersebut. Ini kombinasi terbaik: konsistensi untuk penulis, concurrency untuk pembaca.

Kesalahan Umum

#KesalahanGejalaSolusi
1Update stok tanpa FOR UPDATEDouble spending / stok negatifKunci baris dengan FOR UPDATE
2Memegang lock terlalu lamaQuery lain menumpuk menungguTransaksi pendek, langsung COMMIT
3Lupa COMMITPerubahan "hilang" dari transaksi lainSelalu akhiri dengan COMMIT/ROLLBACK
4Mengharapkan psql tanpa BEGIN berperilaku transaksionalPerubahan langsung terlihatGunakan BEGIN eksplisit untuk multi-step

Penutup

Di episode 11 ini kita sudah memahami fondasi keandalan database: prinsip ACID (Atomicity, Consistency, Isolation, Durability), perintah transaksi BEGIN, COMMIT, ROLLBACK, dan SAVEPOINT, fenomena concurrency dan isolation level, serta locking eksplisit dengan SELECT ... FOR UPDATE dan FOR SHARE untuk mencegah race condition.

Inti yang harus dibawa pulang:

  • ACID menjamin transaksi aman: atomic, konsisten, terisolasi, dan tahan lama.
  • BEGIN → statement → COMMIT atau ROLLBACK adalah pola transaksi yang benar.
  • SAVEPOINT memungkinkan rollback parsial tanpa membatalkan seluruh transaksi.
  • Default PostgreSQL adalah Read Committed; Repeatable Read dan Serializable untuk kebutuhan ketat.
  • SELECT ... FOR UPDATE adalah senjata mencegah double spending dan overbooking.

Di episode 12 selanjutnya, kita masuk ke dunia database objects tingkat lanjut: Views, Materialized Views & Generated Columns — mulai dari standard view untuk menyederhanakan akses data, materialized view untuk query analitik berkecepatan tinggi dengan REFRESH ... CONCURRENTLY, hingga generated columns yang menghitung nilai secara otomatis.

Belajar SQL PostgreSQL - Transaction Management, ACID & Concurrency Control | Belajar SQL PostgreSQL