Lock dan deadlock

Memahami lock tingkat baris, antrean tunggu kunci, dan pendeteksian deadlock.

Daftar isi

Topik ini membahas kunci (lock) sebagai mekanisme yang menjaga penulisan bersamaan tetap benar, mulai dari kunci baris yang diambil UPDATE, antrean tunggu yang terbentuk, sampai deadlock yang terdeteksi otomatis. Sebagian besar kunci bekerja tanpa disentuh, dan tugas penulis query adalah mengenalinya agar tidak menahannya tanpa sadar.

Blok penyiapan dataset#

bash
# siapkan ulang tabel dan data contoh untuk topik ini
PGOPTIONS='-c client_min_messages=warning' psql -q -U postgres -d perpustakaan <<'SQL'
DROP TABLE IF EXISTS peminjaman, buku, anggota, kategori CASCADE;
CREATE TABLE kategori (id SERIAL PRIMARY KEY, nama TEXT NOT NULL UNIQUE);
CREATE TABLE anggota (id SERIAL PRIMARY KEY, nama TEXT NOT NULL, email TEXT UNIQUE, bergabung DATE NOT NULL, aktif BOOLEAN NOT NULL DEFAULT TRUE);
CREATE TABLE buku (id SERIAL PRIMARY KEY, judul TEXT NOT NULL, penulis TEXT NOT NULL, tahun INTEGER NOT NULL, kategori_id INTEGER REFERENCES kategori(id));
CREATE TABLE peminjaman (id SERIAL PRIMARY KEY, anggota_id INTEGER NOT NULL REFERENCES anggota(id), buku_id INTEGER NOT NULL REFERENCES buku(id), tanggal_pinjam DATE NOT NULL, tanggal_kembali DATE);
INSERT INTO kategori (nama) VALUES ('Pemrograman'), ('Sains'), ('Sejarah'), ('Fiksi'), ('Statistika');
INSERT INTO anggota (nama, email, bergabung, aktif) VALUES ('Ali Rahman', '[email protected]', '2024-01-15', TRUE), ('Budi Santoso', '[email protected]', '2024-03-02', TRUE), ('Citra Kirana', '[email protected]', '2024-05-20', FALSE), ('Dewi Lestari', '[email protected]', '2024-08-11', TRUE), ('Eka Pratama', NULL, '2025-01-30', TRUE);
INSERT INTO buku (judul, penulis, tahun, kategori_id) VALUES ('Dasar Basis Data', 'Sari Wulandari', 2021, 1), ('Jaringan Komputer', 'Ahmad Fauzi', 2019, 1), ('Fisika untuk Pemula', 'Rina Marlina', 2020, 2), ('Kimia Organik', 'Rina Marlina', 2022, 2), ('Sejarah Nusantara', 'Bambang Wijaya', 2018, 3), ('Cerita Kota Lama', 'Nur Aisyah', 2023, 4), ('Statistika Terapan', 'Sari Wulandari', 2022, 5), ('Algoritma dan Struktur Data', 'Ahmad Fauzi', 2021, 1);
INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam, tanggal_kembali) VALUES (1, 1, '2025-03-01', '2025-03-15'), (1, 7, '2025-03-05', '2025-03-20'), (2, 3, '2025-03-10', NULL), (3, 5, '2025-02-20', '2025-03-01'), (2, 1, '2025-04-02', NULL), (4, 6, '2025-04-07', NULL), (5, 8, '2025-04-09', '2025-04-25'), (4, 2, '2025-05-01', NULL);
SQL

Kunci yang diambil pernyataan tulis#

UPDATE pada satu baris mengambil kunci barisnya sampai transaksi berakhir, dan transaksi lain yang menyasar baris yang sama menunggu di antrean, sedangkan baris tetangga tetap bebas ditulis. Pembaca tidak pernah menunggu kunci baris karena MVCC melayani versi lama, sehingga kepadatan kunci hanya dirasakan penulis.

Keadaan kunci yang sedang dipegang terbaca pada tampilan pg_locks, dan blok berikut mengunci satu baris secara eksplisit lalu membaca kunci yang dipegangnya pada sesi yang sama.

bash
# kunci baris eksplisit dan kunci yang terbaca
psql -U postgres -d perpustakaan <<'SQL'
BEGIN;
SELECT id FROM anggota WHERE id = 1 FOR UPDATE;
SELECT mode, granted FROM pg_locks WHERE relation = 'anggota'::regclass AND granted;
ROLLBACK;
SQL
text Keluaran
BEGIN
 id
----
  1
(1 row)

     mode     | granted
--------------+---------
 RowShareLock | t
(1 row)

ROLLBACK

Klausa FOR UPDATE mengunci baris yang dipilih sampai COMMIT atau ROLLBACK, dan keluaran pg_locks memuat kunci barisnya beserta kunci tabel yang menopangnya. Pola SELECT FOR UPDATE menjadi cara baku mengunci baris yang akan diperbarui setelah perhitungan, dan kuncinya lepas pada ROLLBACK di akhir blok.

Kunci tingkat tabel#

Selain kunci baris, setiap pernyataan mengambil kunci tingkat tabel yang ringan, misalnya RowExclusiveLock dari UPDATE dan AccessShareLock dari SELECT, dan kunci ini bertingkat menurut kecocokannya. ALTER TABLE dan DROP TABLE menuntut AccessExclusiveLock yang bertabrakan dengan segalanya, sehingga menjalankannya di jam sibuk menahan seluruh lalu lintas, peringatan yang sudah ditekankan pada modul definisi data.

Kunci tabel yang lebih kuat juga diminta eksplisit untuk skenario tertentu, misalnya LOCK TABLE … IN SHARE MODE sebelum pemuatan data yang menuntut tidak ada penulis lain. Pemakaian eksplisit semacam ini jarang dan selalu berjangka pendek, karena kunci yang dipegang lama terlihat langsung oleh seluruh aplikasi.

Deadlock dan pendeteksiannya#

Deadlock terjadi ketika dua transaksi masing-masing menahan kunci yang ditunggu pasangannya, sehingga keduanya tidak pernah maju. Blok berikut menggambarkan urutan dua sesi yang melingkar, dan pola ini tidak dapat direkam sebagai keluaran tetap karena membutuhkan dua sesi yang berjalan bersamaan.

bash
# tidak-dijalankan (deadlock membutuhkan dua sesi yang berjalan bersamaan)
# sesi 1: UPDATE baris id=1, lalu baris id=2
# sesi 2: UPDATE baris id=2, lalu baris id=1
# salah satu sesi menerima error "deadlock detected" setelah deadlock_timeout

PostgreSQL mendeteksi lingkaran tunggu secara otomatis pada interval deadlock_timeout yang bawaannya satu detik, lalu membatalkan salah satu transaksi dengan error yang menyebutkan proses pasangannya, sedangkan transaksi yang tersisa melanjutkan pekerjaannya seolah tidak pernah buntu. Pengulangan transaksi yang menerima error ini menjadi pola wajib aplikasi, sebagaimana kegagalan serialisasi pada tingkat isolasi yang ketat.

Mencegah dengan urutan dan keringkasan#

Pencegahan deadlock yang paling murah adalah mengunci dalam urutan yang konsisten di seluruh aplikasi, misalnya selalu menurut id menaik, karena lingkaran tunggu tidak terbentuk tanpa urutan yang bertentangan. Kebiasaan kedua menjaga transaksi tetap pendek, karena kunci yang dipegang menyeberangi setiap jeda yang ditunggu aplikasinya.

bash
# pembaruan dua baris dalam urutan id yang konsisten
psql -U postgres -d perpustakaan <<'SQL'
BEGIN;
SELECT id, nama FROM anggota WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
UPDATE anggota SET aktif = TRUE WHERE id IN (1, 2);
COMMIT;
SQL
text Keluaran
BEGIN
 id |     nama
----+--------------
  1 | Ali Rahman
  2 | Budi Santoso
(2 rows)

UPDATE 2
COMMIT

SELECT FOR UPDATE dengan ORDER BY id mengunci kedua baris dalam urutan yang sama bagi setiap sesi yang menjalankannya, sehingga dua transaksi serupa tidak pernah saling menunggu silang. Pola ini menggantikan dua UPDATE terpisah yang urutannya ditentukan arus program.

Kesalahan umum#

Kesalahan pertama adalah menjalankan transaksi interaktif yang terbuka selagi menunggu masukan manusia, karena seluruh kunci barisnya tetap dipegang dan antrean menumpuk di belakangnya. Kesalahan kedua adalah urutan kunci yang tidak konsisten antar bagian aplikasi, penyebab deadlock yang paling umum, dan kesepakatan urutan id menjadi penangkalnya.

Kesalahan ketiga adalah mengabaikan error deadlock tanpa pengulangan, padahal transaksi yang dibatalkan perlu diulang utuh untuk menyelesaikan pekerjaannya. Kesalahan keempat adalah menaikkan deadlock_timeout demi menghindari pembatalan, karena penundaan itu membuat sesi lain menunggu lebih lama sebelum lingkaran dipotong, dan nilai bawaannya dipertahankan kecuali ada alasan yang terukur.

Ringkasan#

  • UPDATE mengunci baris yang disentuhnya sampai akhir transaksi, dan penulis lain pada baris yang sama menunggu di antrean.
  • SELECT FOR UPDATE mengunci baris yang akan dihitung lalu diperbarui, dan pg_locks memaparkan kunci yang sedang dipegang.
  • Deadlock terdeteksi otomatis pada deadlock_timeout, salah satu transaksi dibatalkan, dan aplikasi mengulangnya.
  • Urutan kunci yang konsisten dan transaksi yang pendek adalah dua penangkal deadlock yang paling murah.

Referensi#