Kunci membuat transaksi antre dengan tertib — sampai dua transaksi masing-masing memegang kunci yang dibutuhkan lawannya. Keduanya lalu menunggu selamanya: tidak ada yang bisa maju, tidak ada yang menyerah. Kebuntuan melingkar ini disebut deadlock, dan InnoDB menyelesaikannya dengan cara yang tegas: mendeteksi siklusnya, memilih satu transaksi sebagai korban, lalu membatalkannya dengan ERROR 1213. Kebuntuan tidak bisa kita ciptakan dari satu terminal — dua koneksi yang benar-benar bersamaan diperlukan — sehingga pada chapter ini kita simulasikan skenarionya lewat tabel waktu dan diagram, bedah deteksi otomatisnya, lalu susun pola pencegahan yang diuji nyata lewat SELECT ... FOR UPDATE berurutan.
Menyiapkan Tabel Latihan #
Dua rekening dengan saldo berimbang menjadi arena kebuntuan:
DROP DATABASE IF EXISTS belajar_trx;
CREATE DATABASE belajar_trx;
USE belajar_trx;
CREATE TABLE rekening (
id INT AUTO_INCREMENT PRIMARY KEY,
pemilik VARCHAR(50) NOT NULL,
saldo DECIMAL(12,2) NOT NULL
);
INSERT INTO rekening (pemilik, saldo) VALUES ('Budi', 500000.00), ('Siti', 300000.00);
SELECT id, pemilik, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changed
Query OK, 0 rows affected (0.00 sec)
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
+----+---------+-----------+
| id | pemilik | saldo |
+----+---------+-----------+
| 1 | Budi | 500000.00 |
| 2 | Siti | 300000.00 |
+----+---------+-----------+
2 rows in set (0.00 sec)Skenario Klasik: Dua Transfer yang Saling Silang #
Kebuntuan paling klasik lahir dari dua transfer berlawanan arah. Koneksi A mentransfer uang dari Budi ke Siti — mengunci baris Budi dulu, lalu meminta baris Siti. Koneksi B melakukan kebalikannya persis pada saat yang sama — mengunci baris Siti dulu, lalu meminta baris Budi. Diagram berikut memetakan pertemuan keduanya:
Keempat panah tengah membentuk lingkaran tunggu: A menunggu B, B menunggu A. Tidak ada urutan pelaksanaan mana pun yang bisa memutus lingkaran itu tanpa bantuan pihak ketiga — dan pihak ketiga itulah InnoDB. Tabel waktu berikut merinci peristiwanya langkah demi langkah:
| Waktu | Koneksi A | Koneksi B | Keadaan kunci |
|---|---|---|---|
| t1 | UPDATE ... WHERE id = 1; — sukses | baris 1 terkunci oleh A | |
| t2 | UPDATE ... WHERE id = 2; — sukses | baris 2 terkunci oleh B | |
| t3 | UPDATE ... WHERE id = 2; — menggantung | A menunggu B melepas baris 2 | |
| t4 | UPDATE ... WHERE id = 1; — menggantung | B menunggu A melepas baris 1 — siklus terbentuk | |
| t5 | InnoDB mendeteksi siklus dan memilih korban | korban: transaksi dengan undo paling kecil | |
| t6 | ERROR 1213 — transaksi A dibatalkan | pembaruan B dilanjutkan, sukses | kunci A dilepas, B selesai |
Pada t5, sistem deteksi kebuntuan InnoDB — aktif secara bawaan lewat variabel innodb_deadlock_detect — memindai graf tunggu, menemukan siklus, dan membatalkan satu transaksi peserta. Pilihan korban umumnya jatuh pada transaksi yang paling murah digulirkan ulang, yaitu yang telah mengubah baris paling sedikit. Nasib korban dikabarkan lewat error yang terkenal itu; versi dua terminalnya tampak seperti ilustrasi berikut (blok tidak dieksekusi karena membutuhkan dua terminal sungguhan):
-- @notest
-- Terminal kiri (Koneksi A - korban kebuntuan)
START TRANSACTION;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction-- @notest
-- Terminal kanan (Koneksi B - pemenang kebuntuan)
START TRANSACTION;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 2;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 1;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0Koneksi A menerima ERROR 1213 (40001) di statement keduanya — seluruh transaksinya dibatalkan, termasuk UPDATE pertama yang sebelumnya sukses. Koneksi B tidak menerima error apa pun; statement keduanya sempat menggantung, lalu dilanjutkan begitu kunci A dilepas, dan transaksinya selesai dengan sukses. Inilah sifat penting kebuntuan: selalu ada pihak yang kalah tanpa perlawanan, dan pihak itu harus mengulang seluruh transaksinya dari awal.
Membaca Jejak Kebuntuan #
Sistem produksi perlu tahu kebuntuan apa yang barusan terjadi. Perintah SHOW ENGINE INNODB STATUS menyajikan bagian LATEST DETECTED DEADLOCK yang memuat dua transaksi peserta beserta kunci yang mereka pegang dan tunggu — peta lengkap untuk mendiagnosis pola akses mana yang bertabrakan. Outputnya panjang dan berubah setiap kejadian baru, sehingga tidak layak dibandingkan baris demi baris seperti output latihan kita; cukup kita ketahui cara membacanya: dua blok transaksi ditampilkan dengan daftar kunci masing-masing, dan urutannya mengisahkan graf tunggu yang sama seperti tabel t1 sampai t6 di atas. Untuk pengawasan berkelanjutan, opsi innodb_print_all_deadlocks bisa diaktifkan agar setiap kebuntuan ikut tercatat di error log, bukan hanya yang terakhir.
Mencegah Deadlock: Urutan Akses Konsisten #
Akar kebuntuan pada skenario kita adalah dua koneksi mengunci baris yang sama dengan urutan berlawanan. Obatnya sederhana dan ampuh: seluruh kode mengunci sumber daya dalam urutan yang sama — misalnya selalu dari id terkecil ke terbesar. Pola ini bisa kita uji nyata dengan membaca dan mengunci kedua baris sekaligus dalam urutan terjamin:
START TRANSACTION;
SELECT id, saldo FROM rekening WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
UPDATE rekening SET saldo = saldo - 100000 WHERE id = 1;
UPDATE rekening SET saldo = saldo + 100000 WHERE id = 2;
COMMIT;
SELECT id, pemilik, saldo FROM rekening ORDER BY id;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
+----+-----------+
| id | saldo |
+----+-----------+
| 1 | 500000.00 |
| 2 | 300000.00 |
+----+-----------+
2 rows in set (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Query OK, 0 rows affected (0.00 sec)
+----+---------+-----------+
| id | pemilik | saldo |
+----+---------+-----------+
| 1 | Budi | 400000.00 |
| 2 | Siti | 400000.00 |
+----+---------+-----------+
2 rows in set (0.00 sec)Baris pertama pola ini membaca kedua rekening dengan FOR UPDATE dan ORDER BY id — dua baris terkunci eksklusif dalam satu statement, sebelum pekerjaan apa pun dimulai. Koneksi lain yang menjalankan pola yang sama akan berhenti di statement pembuka ini dan mengantre rapi, bukan menyusup di tengah jalan; karena semua pemakai pola ini meminta kunci dengan urutan identik, tidak mungkin terbentuk lingkaran tunggu. Transfer pun tuntas: saldo akhir Rp400.000,00 dan Rp400.000,00.
Transaksi Pendek dan Index yang Tepat #
Dua kebiasaan pelengkap menutup celah kebuntuan yang tersisa. Pertama, transaksi pendek: makin lama transaksi memegang kunci, makin lebar jendela waktu bagi tabrakan — jangan pernah menunggu input pengguna, memanggil layanan luar, atau menghitung berat di tengah transaksi; kumpulkan dulu semua keputusan, bungkus pekerjaan database sesingkat mungkin, lalu COMMIT. Kedua, index yang tepat: statement yang menjalankan WHERE tanpa index memindai lebih banyak baris dan ikut mengunci lebih banyak baris (serta gap di antaranya) daripada yang dibutuhkan — dua transaksi yang seharusnya hanya bertabrakan pada satu baris bisa saling mengunci puluhan baris hanya karena kolom filternya tidak terindeks. Transaksi pendek memperkecil peluang, index tepat memperkecil wilayah tabrakan; keduanya bekerja bersama urutan akses konsisten sebagai pagar tiga lapis.
Membersihkan Database Latihan #
Rangkaian latihan selesai dan database latihan kita jatuhkan kembali:
DROP DATABASE belajar_trx;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Referensi #
- Deadlocks in InnoDB — MySQL 8.4 Reference Manual
- Deadlock Detection — MySQL 8.4 Reference Manual
- InnoDB Locking — MySQL 8.4 Reference Manual
Kebuntuan kini terpetakan utuh: skenario silang dua transfer, siklus tunggu yang hanya bisa diputus oleh deteksi otomatis InnoDB, nasib korban ERROR 1213 yang harus mengulang transaksinya, dan pagar pencegahan tiga lapis — urutan akses konsisten, transaksi pendek, index yang tepat. Satu pertanyaan penutup menggantung: mengulang transaksi yang sudah setengah jalan bukan pekerjaan sepele — bagaimana aplikasi tahu ulangan mana yang aman, dan pola apa yang dipakai ketika banyak pengguna memperebutkan baris yang sama? Chapter penutup section ini merangkai semuanya menjadi strategi penanganan konkurensi yang utuh.