Transaction di Postgres
Transaksi di PostgreSQL adalah unit kerja logis yang terdiri dari satu atau lebih pernyataan SQL yang dijalankan sebagai satu kesatuan. Tujuan utamanya adalah untuk menjaga konsistensi, isolasi, dan integritas data.
π Konsep Transaksi di PostgreSQL
1. ACID
Transaksi harus memenuhi sifat ACID:
Atomicity: Semua pernyataan dalam transaksi berhasil semua atau dibatalkan semua.
Consistency: Data tetap konsisten sebelum dan sesudah transaksi.
Isolation: Transaksi tidak terlihat oleh transaksi lain sampai selesai.
Durability: Setelah commit, data tersimpan permanen.
2. Perintah Dasar
BEGIN; -- Memulai transaksi
-- Perintah SQL
COMMIT; -- Menyimpan perubahan
ROLLBACK; -- Membatalkan semua perubahan
π Apa itu FOR UPDATE
?
FOR UPDATE
?FOR UPDATE
digunakan untuk mengunci baris yang dipilih dalam transaksi agar tidak bisa diubah oleh transaksi lain sampai transaksi selesai (commit/rollback).
Tujuannya:
Mencegah kondisi race condition (misalnya dua pengguna mengedit data yang sama).
Memastikan data yang dibaca tidak berubah sebelum transaksi selesai.
π― Contoh dan Ilustrasi
1. Setup Tabel
CREATE TABLE akun (
id SERIAL PRIMARY KEY,
nama VARCHAR(50),
saldo INTEGER
);
INSERT INTO akun (nama, saldo) VALUES
('Alice', 1000),
('Bob', 1000);
2. Simulasi Transaksi tanpa FOR UPDATE
FOR UPDATE
Terminal 1:
BEGIN;
SELECT saldo FROM akun WHERE nama = 'Alice'; -- dapat saldo 1000
-- bayangkan proses lanjut menghitung dan menyimpan
UPDATE akun SET saldo = 900 WHERE nama = 'Alice';
-- COMMIT nanti
Terminal 2 (dijalankan sebelum commit Terminal 1):
BEGIN;
SELECT saldo FROM akun WHERE nama = 'Alice'; -- juga dapat saldo 1000!
UPDATE akun SET saldo = 800 WHERE nama = 'Alice';
COMMIT;
Masalah: Kedua transaksi menganggap saldo awal 1000 dan memperbarui berdasarkan data yang sudah kadaluarsa β terjadi inconsistency.
3. Simulasi Transaksi dengan FOR UPDATE
FOR UPDATE
Terminal 1:
BEGIN;
SELECT saldo FROM akun WHERE nama = 'Alice' FOR UPDATE;
-- Alice dikunci barisnya sampai transaksi ini selesai
UPDATE akun SET saldo = 900 WHERE nama = 'Alice';
-- Jangan commit dulu
Terminal 2:
BEGIN;
SELECT saldo FROM akun WHERE nama = 'Alice' FOR UPDATE;
-- Akan menunggu sampai transaksi di Terminal 1 selesai (commit/rollback)
Keuntungan: Data tidak bisa dibaca dan diproses oleh transaksi lain sebelum transaksi pertama selesai β race condition dihindari.
π§ͺ Praktek Langsung: Transfer Saldo
Misal: Transfer 100 dari Alice ke Bob
BEGIN;
-- Kunci baris yang ingin dimodifikasi
SELECT * FROM akun WHERE nama IN ('Alice', 'Bob') FOR UPDATE;
-- Dapatkan saldo
SELECT saldo FROM akun WHERE nama = 'Alice'; -- 1000
SELECT saldo FROM akun WHERE nama = 'Bob'; -- 1000
-- Update
UPDATE akun SET saldo = saldo - 100 WHERE nama = 'Alice';
UPDATE akun SET saldo = saldo + 100 WHERE nama = 'Bob';
COMMIT;
Jika terjadi error, bisa langsung:
ROLLBACK;
π§ Kesimpulan
Transaksi
Sekumpulan perintah SQL yang dieksekusi sebagai satu kesatuan
BEGIN/COMMIT
Memulai dan menyelesaikan transaksi
ROLLBACK
Membatalkan transaksi
FOR UPDATE
Mengunci baris yang dipilih untuk mencegah konflik antar transaksi
β οΈ Apa Itu Deadlock?
Deadlock terjadi ketika dua transaksi saling menunggu satu sama lain untuk melepaskan kunci, sehingga tidak ada yang bisa lanjut.
π Ilustrasi Kasus Deadlock
Misalkan kita punya tabel akun
yang sama:
CREATE TABLE akun (
id SERIAL PRIMARY KEY,
nama VARCHAR(50),
saldo INTEGER
);
π§ͺ Simulasi Deadlock dengan FOR UPDATE
FOR UPDATE
Transaksi 1 (T1):
BEGIN;
SELECT * FROM akun WHERE nama = 'Alice' FOR UPDATE;
-- tunggu beberapa detik
SELECT * FROM akun WHERE nama = 'Bob' FOR UPDATE;
Transaksi 2 (T2):
BEGIN;
SELECT * FROM akun WHERE nama = 'Bob' FOR UPDATE;
-- tunggu beberapa detik
SELECT * FROM akun WHERE nama = 'Alice' FOR UPDATE;
π Hasil:
T1 mengunci Alice β tunggu Bob
T2 mengunci Bob β tunggu Alice
Keduanya saling tunggu β deadlock
PostgreSQL akan memilih satu untuk dibatalkan (
ERROR: deadlock detected
)
π οΈ Kenapa Bisa Deadlock Walau Pakai FOR UPDATE
?
FOR UPDATE
?Karena FOR UPDATE
mengunci baris secara eksklusif. Kalau dua transaksi mengunci dengan urutan berbeda, maka bisa saling tunggu. Urutan pengambilan kunci itu penting.
β
Solusi: Gunakan FOR NO KEY UPDATE
FOR NO KEY UPDATE
β Apa Itu FOR NO KEY UPDATE
?
FOR NO KEY UPDATE
?FOR NO KEY UPDATE
adalah varian lebih ringan dariFOR UPDATE
.Mengunci baris hanya untuk update nilai kolom, tanpa mengizinkan update kunci (misalnya kolom primary key).
Ini mengurangi kemungkinan deadlock karena kunci yang digunakan tidak terlalu agresif.
π Perbandingan Lock Mode
FOR UPDATE
Mengunci baris untuk update penuh (termasuk kunci)
FOR NO KEY UPDATE
Mengunci baris untuk update non-kunci (lebih ringan)
FOR SHARE
Hanya baca data, bisa dibaca transaksi lain
FOR KEY SHARE
Bisa dibaca dan referensi foreign key, tapi tidak bisa dihapus/dikunci penuh
π§ͺ Simulasi dengan FOR NO KEY UPDATE
FOR NO KEY UPDATE
Jika transaksi hanya ingin update saldo (bukan id
), maka lebih aman:
T1:
BEGIN;
SELECT * FROM akun WHERE nama = 'Alice' FOR NO KEY UPDATE;
-- tunggu
SELECT * FROM akun WHERE nama = 'Bob' FOR NO KEY UPDATE;
T2:
BEGIN;
SELECT * FROM akun WHERE nama = 'Bob' FOR NO KEY UPDATE;
-- tunggu
SELECT * FROM akun WHERE nama = 'Alice' FOR NO KEY UPDATE;
β οΈ Hasil:
Deadlock masih mungkin terjadi kalau urutan ambil lock-nya berbeda, meskipun lebih jarang.
Maka kunci solusi utamanya tetap: urutkan pengambilan kunci secara konsisten!
β
Best Practice Hindari Deadlock
Selalu akses resource dalam urutan yang sama (misal: urut berdasarkan
id
ASC).Gunakan lock mode serendah mungkin (
FOR NO KEY UPDATE
lebih baik dariFOR UPDATE
).Gunakan timeout / pengecekan deadlock dan rollback otomatis jika perlu.
π Kesimpulan
FOR UPDATE
kuat tapi agresif β rawan deadlock jika resource diakses tidak konsisten.FOR NO KEY UPDATE
lebih ringan β cocok jika hanya update kolom non-kunci.Urutan pengambilan kunci lebih penting dari jenis lock-nya.
π Apa Maksudnya "Pengambilan Kunci Secara Konsisten"?
Artinya: semua transaksi yang mengakses banyak baris/data harus mengunci data dalam urutan yang sama, misalnya:
berdasarkan
id
ASCberdasarkan abjad nama
urutan waktu tertentu
β Jika Transaksi A mengunci data dengan urutan A β B dan Transaksi B mengunci data dengan urutan B β A, β οΈ maka potensi deadlock sangat tinggi.
β Jika semua transaksi selalu mengunci A β B, maka tidak akan ada saling tunggu silang.
π Contoh Deadlock karena Kunci Tidak Konsisten
Misal tabel akun
:
id | nama | saldo
---+--------+-------
1 | Alice | 1000
2 | Bob | 1000
Transaksi A
BEGIN;
SELECT * FROM akun WHERE id = 1 FOR UPDATE; -- kunci Alice
SELECT * FROM akun WHERE id = 2 FOR UPDATE; -- kunci Bob
Transaksi B
BEGIN;
SELECT * FROM akun WHERE id = 2 FOR UPDATE; -- kunci Bob
SELECT * FROM akun WHERE id = 1 FOR UPDATE; -- tunggu Alice
π Keduanya saling tunggu β Deadlock!
β
Solusi: Ambil Kunci Berdasarkan Urutan Tetap
Cara Aman:
-- Ambil data dengan urutan konsisten (id ASC misalnya)
SELECT * FROM akun WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
Jadi, transaksi A dan B:
BEGIN;
SELECT * FROM akun WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- lanjut proses transfer atau update
COMMIT;
β Hasil: tidak ada saling tunggu silang, karena kedua transaksi:
mengakses
id = 1
lebih dulubaru
id = 2
berikutnya
π§ͺ Contoh Praktis: Transfer Saldo Aman
-- Asumsikan kita mau transfer antar 2 akun
-- Ambil id akun pengirim dan penerima, lalu sort ASC
-- Misal id_pengirim = 2, id_penerima = 1
-- Sort dulu:
SELECT LEAST(2, 1) AS id1, GREATEST(2, 1) AS id2;
-- Gunakan urutan itu untuk locking
BEGIN;
-- Ambil dan kunci dua akun berdasarkan urutan id
SELECT * FROM akun WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- Update saldo
UPDATE akun SET saldo = saldo - 100 WHERE id = 2;
UPDATE akun SET saldo = saldo + 100 WHERE id = 1;
COMMIT;
π§ Ringkasan Tips
π Gunakan urutan tetap
Gunakan ORDER BY
berdasarkan id
, nama
, atau kriteria stabil lainnya
π« Jangan kunci berbeda-beda urutan
Hindari transaksi A β B dan B β A
βοΈ Gunakan LEAST
/ GREATEST
Untuk menentukan siapa dikunci lebih dulu secara deterministik
π Gabungkan dalam satu query
Semakin sedikit query terpisah, semakin kecil peluang deadlock
π‘ Gunakan lock ringan (NO KEY UPDATE
)
Kalau tidak perlu ubah PK atau referensi, pakai lock lebih ringan
Last updated