MySQL Error 1213 Deadlock — transaction was rolled back
Deadlock found — transaction automatically rolled back
Verified against MySQL 8.0 InnoDB Deadlock documentation, MySQL error reference — Error 1213, Percona deadlock troubleshooting guide · Updated June 2026
> quick_fix
Error 1213 means two or more transactions each hold a lock that the other needs, creating a circular wait. InnoDB detects this and rolls back the transaction with the fewest changes (the 'victim'). The fix: retry the rolled-back transaction (it will succeed now that the other transaction completed), then prevent future deadlocks by ensuring all transactions lock rows in the same order.
-- See the last deadlock details
SHOW ENGINE INNODB STATUS\G
-- Look for the 'LATEST DETECTED DEADLOCK' section
-- Retry pattern in application code (pseudocode):
-- for attempt in range(3):
-- try:
-- execute_transaction()
-- break
-- except DeadlockError:
-- if attempt == 2: raise
-- sleep(random(0.01, 0.1))What causes this error
A deadlock occurs when Transaction A holds Lock 1 and waits for Lock 2, while Transaction B holds Lock 2 and waits for Lock 1. Neither can proceed. InnoDB's deadlock detector runs continuously and resolves this by rolling back the transaction that has done the least work (fewest undo log entries). The rolled-back transaction receives error 1213. Common triggers: two transactions updating the same rows in different order; gap locks on non-unique indexes causing phantom read prevention locks that block inserts; SELECT ... FOR UPDATE queries locking rows in unpredictable order due to missing or different indexes; and bulk UPDATE/DELETE operations on large tables that acquire many locks simultaneously.
How to fix it
- 01
step 1
Read the deadlock report from InnoDB
InnoDB logs the last deadlock with full details — which transactions were involved, which locks they held, which locks they were waiting for, and which transaction was rolled back.
-- Get the full InnoDB status (includes deadlock section) SHOW ENGINE INNODB STATUS\G -- Key sections to read: -- 'LATEST DETECTED DEADLOCK' -- TRANSACTION 1: shows the SQL, locks held, lock waiting for -- TRANSACTION 2: shows the SQL, locks held, lock waiting for -- 'WE ROLL BACK TRANSACTION N': which one InnoDB killed -- Enable deadlock logging to error log (persistent) SET GLOBAL innodb_print_all_deadlocks = ON; - 02
step 2
Identify the conflicting lock order
The deadlock report shows the exact rows and indexes involved. Compare the lock acquisition order of both transactions — they're locking the same resources in opposite order.
-- Example deadlock scenario: -- Transaction A: UPDATE orders SET status='shipped' WHERE user_id=1; -- UPDATE inventory SET qty=qty-1 WHERE product_id=42; -- Transaction B: UPDATE inventory SET qty=qty-1 WHERE product_id=42; -- UPDATE orders SET status='shipped' WHERE user_id=2; -- Both lock orders -> inventory in different positions -- Fix: always lock orders first, then inventory -- Transaction A & B should both do: -- UPDATE orders ... (locks orders row first) -- UPDATE inventory ... (locks inventory row second) - 03
step 3
Add retry logic for deadlocked transactions
Deadlocks can't be 100% eliminated in concurrent systems. The rolled-back transaction should be retried — it will succeed because the competing transaction has completed and released its locks.
import time, random import mysql.connector from mysql.connector import errors def execute_with_retry(cursor, conn, sql, params, max_retries=3): for attempt in range(max_retries): try: cursor.execute(sql, params) conn.commit() return except errors.DatabaseError as e: if e.errno == 1213 and attempt < max_retries - 1: conn.rollback() time.sleep(random.uniform(0.01, 0.1 * (attempt + 1))) else: raise - 04
step 4
Reduce lock scope and duration
The less time a transaction holds locks, the smaller the deadlock window. Keep transactions short, avoid user interaction mid-transaction, and use the narrowest possible WHERE clause.
-- BAD: locks many rows with a range scan START TRANSACTION; SELECT * FROM orders WHERE created_at > '2026-01-01' FOR UPDATE; -- ... application logic ... UPDATE orders SET processed = 1 WHERE created_at > '2026-01-01'; COMMIT; -- GOOD: lock only the specific rows you need START TRANSACTION; SELECT * FROM orders WHERE id IN (101, 102, 103) FOR UPDATE; UPDATE orders SET processed = 1 WHERE id IN (101, 102, 103); COMMIT; -- GOOD: add an index so the lock targets specific rows ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);
How to verify the fix
- SHOW ENGINE INNODB STATUS shows no new deadlocks after the fix.
- Application logs confirm retry logic is catching and recovering from error 1213.
- Transactions that previously deadlocked now acquire locks in a consistent order.
- innodb_print_all_deadlocks is ON so future deadlocks are logged for monitoring.
Why 1213 happens at the runtime level
InnoDB uses row-level locking with a wait-for graph to track which transactions are waiting for which locks. When the wait-for graph contains a cycle (A waits for B, B waits for A — or longer chains), InnoDB's deadlock detector identifies the cycle and selects a victim transaction to roll back. The victim selection algorithm chooses the transaction with the fewest undo log records (least work done), making it the cheapest to roll back. The victim receives error 1213 (ER_LOCK_DEADLOCK, SQLSTATE 40001). Gap locks (used by REPEATABLE READ isolation to prevent phantom reads) are a common source of non-obvious deadlocks because they lock ranges of index values, not just individual rows.
Common debug mistakes for 1213
- Increasing innodb_lock_wait_timeout thinking it will fix deadlocks — that setting only affects lock wait timeouts (error 1205), not deadlocks (error 1213). Deadlocks are detected and resolved instantly regardless of this setting.
- Wrapping entire business processes in a single long transaction — the longer a transaction holds locks, the higher the probability of deadlock with another transaction.
- Using SELECT ... FOR UPDATE without an index on the WHERE clause — this causes a full table scan that locks every row it examines, massively increasing the deadlock surface.
- Catching error 1213 and immediately retrying without a random backoff delay — both transactions retry at the same time and deadlock again in a livelock pattern.
- Lowering the transaction isolation level to READ COMMITTED as a blanket fix — this reduces gap lock deadlocks but introduces phantom read bugs that are harder to diagnose.
When 1213 signals a deeper problem
Frequent deadlocks on the same tables signal that the application's data access pattern doesn't match the database's locking granularity. The root cause is usually one of three architectural issues: (1) a queueing pattern implemented with SELECT FOR UPDATE on a hot table, where multiple workers compete for the next row — replace with a proper message queue; (2) a denormalized design where updating one entity requires touching multiple tables in different orders across different code paths — standardize the update order in a single service layer; (3) batch operations that lock thousands of rows in a single transaction, colliding with single-row transactions — break batches into smaller chunks with explicit COMMIT between each chunk.
Editor's take
Deadlocks are one of the few database errors where the correct response is 'retry and move on' rather than 'find and fix the bug.' InnoDB's deadlock detection is fast and reliable — it costs you a rolled-back transaction and a few milliseconds of retry delay. If you're seeing fewer than one deadlock per minute under production load, retries are the right fix.
But when deadlocks become frequent, the first diagnostic step is always SHOW ENGINE INNODB STATUS. The deadlock report tells you the exact SQL statements, the exact locks held, and the exact locks waited for. Read it carefully — the answer is always there. I've seen engineers spend hours adding logging and tracing when the deadlock report already contained the root cause.
The most common pattern I see: two code paths update the same two tables in different order. One path does orders first, then inventory. Another path does inventory first, then orders. Under low concurrency this works fine. Under load, deadlocks appear. The fix is always the same: pick one order and enforce it everywhere.
Gap locks are the sneakiest source of deadlocks. Under REPEATABLE READ (MySQL's default), InnoDB locks gaps between index values to prevent phantom reads. Two transactions inserting into the same gap can deadlock even though they're inserting different rows. If you see deadlocks on INSERT statements with no obvious row overlap, gap locks are almost certainly the cause. Switching to READ COMMITTED isolation eliminates gap locks but requires understanding the phantom read implications for your application.
By Bikram Nath · Curator · Updated June 2026
Frequently asked questions
Is a deadlock a bug or normal behavior?
Both. Occasional deadlocks under high concurrency are normal and expected — that's why retry logic exists. But frequent deadlocks (multiple per minute) indicate a lock ordering problem or overly broad transactions that should be fixed at the application level. Treat frequent deadlocks as a performance bug.
How is a deadlock different from a lock wait timeout?
A deadlock (error 1213) is a circular wait detected immediately by InnoDB's deadlock detector — resolution is instant. A lock wait timeout (error 1205) means a transaction waited for a lock held by another transaction for longer than innodb_lock_wait_timeout (default 50 seconds) without any circular dependency. Deadlock = instant detection and rollback. Lock wait timeout = slow detection after a long wait.
Can I prevent all deadlocks?
In theory, yes — if every transaction acquires locks in the exact same global order, deadlocks are impossible. In practice, this is extremely difficult to enforce across a large application. The pragmatic approach: use consistent lock ordering where possible, keep transactions short, and always implement retry logic for error 1213.