September 27, 2026
Transaksi di MySQL (InnoDB).
ACID, START TRANSACTION/COMMIT/ROLLBACK, isolation level dan anomalinya, locking di InnoDB, SELECT ... FOR UPDATE, serta cara menangani deadlock.
Transaksi adalah sekumpulan perintah SQL yang diperlakukan sebagai satu unit kerja: semuanya berhasil, atau tidak ada yang berlaku sama sekali. Contoh klasiknya transfer saldo: mengurangi saldo A dan menambah saldo B harus terjadi bersamaan. Kalau server mati di tengah jalan, uang tidak boleh hilang.
Catatan ini untuk MySQL 8 dengan engine InnoDB. MyISAM tidak mendukung transaksi.
1. ACID
| Sifat | Arti | Di InnoDB dijamin oleh |
|---|---|---|
| Atomicity | Semua atau tidak sama sekali | Undo log, ROLLBACK |
| Consistency | Data selalu berpindah dari satu keadaan valid ke keadaan valid lain | Constraint (PK, FK, NOT NULL, CHECK) + logika aplikasi |
| Isolation | Transaksi yang berjalan bersamaan tidak saling mengacaukan | Isolation level, MVCC, lock |
| Durability | Setelah COMMIT, data tidak hilang walau server crash | Redo log, innodb_flush_log_at_trx_commit=1 (default) |
2. Perintah Dasar
START TRANSACTION; -- atau BEGIN;
UPDATE accounts SET balance = balance - 100000 WHERE id = 1;
UPDATE accounts SET balance = balance + 100000 WHERE id = 2;
COMMIT; -- simpan permanen
-- ROLLBACK; -- atau batalkan semuanya
Savepoint untuk membatalkan sebagian:
START TRANSACTION;
INSERT INTO orders (customer_id, total) VALUES (1, 50000);
SAVEPOINT sebelum_item;
INSERT INTO order_items (order_id, product_id, qty) VALUES (LAST_INSERT_ID(), 99, 1);
ROLLBACK TO SAVEPOINT sebelum_item; -- hanya item yang batal
COMMIT;
Autocommit
Secara default autocommit = 1: setiap statement di luar START TRANSACTION langsung di-commit. Jika kamu mematikannya (SET autocommit = 0), transaksi baru terbuka otomatis dan tetap terbuka sampai kamu COMMIT, dan ini sumber lock yang tertahan lama.
Implicit commit
Statement DDL seperti CREATE TABLE, ALTER TABLE, DROP, TRUNCATE memicu commit otomatis atas transaksi yang sedang berjalan. Jangan campur DDL dan DML dalam satu transaksi dengan harapan bisa di-rollback.
Contoh di aplikasi (PHP PDO)
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
$pdo->beginTransaction();
$pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')->execute([100000, 1]);
$pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')->execute([100000, 2]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
Di Laravel cukup DB::transaction(function () { ... }, 3);. Argumen kedua adalah jumlah percobaan ulang jika terjadi deadlock.
3. Anomali Konkurensi
Saat dua transaksi berjalan bersamaan, beberapa masalah bisa muncul:
| Anomali | Penjelasan |
|---|---|
| Dirty read | Membaca data yang belum di-commit transaksi lain (dan mungkin nanti di-rollback) |
| Non-repeatable read | Membaca baris yang sama dua kali, hasilnya berbeda karena transaksi lain meng-update dan commit di antaranya |
| Phantom read | Menjalankan query rentang dua kali, muncul baris baru karena transaksi lain melakukan insert |
| Lost update | Dua transaksi membaca nilai yang sama, masing-masing menulis hasil hitungannya, salah satu tertimpa |
4. Isolation Level
| Level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
READ UNCOMMITTED | Mungkin | Mungkin | Mungkin |
READ COMMITTED | Tidak | Mungkin | Mungkin |
REPEATABLE READ (default InnoDB) | Tidak | Tidak | Lihat catatan |
SERIALIZABLE | Tidak | Tidak | Tidak |
Catatan khusus InnoDB pada REPEATABLE READ:
- Consistent read (SELECT biasa) membaca snapshot yang dibuat saat pembacaan pertama dalam transaksi (MVCC). Jadi SELECT biasa tidak melihat baris baru dari transaksi lain, sehingga phantom tidak terlihat.
- Locking read (
FOR UPDATE/FOR SHARE),UPDATE, danDELETEmembaca data terbaru dan memasang next-key lock untuk mencegah insert ke rentang yang sedang dibaca.
Mengecek dan mengubah isolation level:
SELECT @@transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- hanya untuk transaksi berikutnya
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
READ COMMITTED sering dipakai di sistem dengan tulis tinggi karena memasang lebih sedikit gap lock, sehingga deadlock lebih jarang. Konsekuensinya, aplikasi harus siap menghadapi non-repeatable read.
5. Locking di InnoDB
InnoDB memakai row-level lock, bukan mengunci seluruh tabel.
| Lock | Sifat |
|---|---|
| Shared (S) | Boleh dibaca bersama, tidak boleh ditulis pihak lain |
| Exclusive (X) | Hanya pemegang lock yang boleh membaca-dengan-lock atau menulis |
| Record lock | Mengunci satu entri index |
| Gap lock | Mengunci “celah” di antara entri index agar tidak ada insert |
| Next-key lock | Record lock + gap lock sebelumnya (default untuk locking read di REPEATABLE READ) |
Penting: Lock dipasang pada index, bukan pada baris mentah. Jika
WHEREdiUPDATEtidak memakai index, InnoDB harus memindai (dan mengunci) jauh lebih banyak baris, bahkan bisa semua baris tabel. Index yang tepat juga berarti lock yang lebih sempit.
6. SELECT … FOR UPDATE: Mencegah Lost Update
Kasus: stok produk tinggal 1, dua pembeli checkout bersamaan.
Salah (baca lalu tulis tanpa lock):
SELECT stock FROM products WHERE id = 7; -- dua transaksi sama-sama dapat 1
UPDATE products SET stock = 0 WHERE id = 7; -- keduanya "berhasil" membeli
Benar (kunci baris saat membaca):
START TRANSACTION;
SELECT stock FROM products WHERE id = 7 FOR UPDATE; -- transaksi kedua menunggu di sini
-- aplikasi cek: stock > 0 ?
UPDATE products SET stock = stock - 1 WHERE id = 7;
COMMIT;
Alternatif tanpa lock eksplisit, dengan update atomik bersyarat:
UPDATE products SET stock = stock - 1 WHERE id = 7 AND stock > 0;
-- affected rows = 0 → stok habis
Varian locking read di MySQL 8:
| Sintaks | Perilaku |
|---|---|
FOR UPDATE | Lock eksklusif, pihak lain yang ingin lock harus menunggu |
FOR SHARE | Lock bersama (pengganti LOCK IN SHARE MODE) |
FOR UPDATE NOWAIT | Langsung error jika baris sedang terkunci |
FOR UPDATE SKIP LOCKED | Lewati baris yang terkunci, cocok untuk antrean job |
Contoh worker antrean yang aman dijalankan paralel:
START TRANSACTION;
SELECT id FROM jobs
WHERE status = 'queued'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED;
UPDATE jobs SET status = 'running' WHERE id = ?;
COMMIT;
7. Deadlock
Deadlock terjadi saat dua transaksi saling menunggu lock yang dipegang lawannya:
T1: UPDATE accounts ... WHERE id = 1; -- lock baris 1
T2: UPDATE accounts ... WHERE id = 2; -- lock baris 2
T1: UPDATE accounts ... WHERE id = 2; -- menunggu T2
T2: UPDATE accounts ... WHERE id = 1; -- menunggu T1 → deadlock
InnoDB mendeteksinya otomatis dan me-rollback salah satu transaksi (yang lebih “kecil”) dengan error:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Melihat deadlock terakhir:
SHOW ENGINE INNODB STATUS\G
-- cari bagian "LATEST DETECTED DEADLOCK"
Mencatat semua deadlock ke error log:
SET GLOBAL innodb_print_all_deadlocks = ON;
Mengurangi deadlock
- Akses baris dalam urutan yang konsisten (misalnya selalu urut
idnaik). Pada contoh transfer, kunci akun dengan id lebih kecil dulu. - Transaksi sependek mungkin. Jangan memanggil API eksternal atau menunggu input user di dalam transaksi.
- Index yang tepat agar lock yang dipasang sedikit.
- Pertimbangkan
READ COMMITTEDuntuk mengurangi gap lock. - Siapkan retry di aplikasi. Deadlock bukan bug fatal, melainkan kondisi normal di sistem konkuren; ulangi transaksi dari awal.
Lock wait timeout vs deadlock
| Deadlock (1213) | Lock wait timeout (1205) | |
|---|---|---|
| Penyebab | Saling tunggu melingkar | Menunggu lock lebih lama dari innodb_lock_wait_timeout (default 50 detik) |
| Yang di-rollback | Seluruh transaksi | Secara default hanya statement terakhir |
Poin kedua sering menjebak: setelah error 1205, transaksi masih terbuka dengan perubahan sebelumnya. Aplikasi harus melakukan ROLLBACK eksplisit (atau set innodb_rollback_on_timeout=ON di konfigurasi server).
Mencari transaksi yang menahan lock:
SELECT * FROM sys.innodb_lock_waits\G
SELECT trx_id, trx_started, trx_mysql_thread_id, trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started;
8. Kesalahan Umum
| Masalah | Solusi |
|---|---|
Transaksi lupa di-commit (misalnya koneksi autocommit=0 di GUI) | Selalu akhiri dengan COMMIT/ROLLBACK; cek INNODB_TRX untuk transaksi lama |
| Memanggil HTTP/email di dalam transaksi | Lakukan setelah commit (atau pakai queue ) |
Mengira ALTER TABLE bisa di-rollback | DDL memicu implicit commit |
| Tabel MyISAM ikut dalam transaksi | Perubahannya tidak bisa di-rollback; konversi ke InnoDB |
| Menangkap exception tapi tidak rollback | Pola try { commit } catch { rollBack; throw } |
| Hitung saldo di aplikasi lalu tulis balik | Gunakan FOR UPDATE atau update atomik SET x = x - ? |
Referensi

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.