MySQL

How to Resolve "Deadlock Found When Trying to Get Lock" in MySQL

4 min read by DebuggedIt

Quick answer

Two transactions each hold a lock the other one needs, and MySQL detects the standoff and kills one of them rather than letting both wait forever. A deadlock...

Two transactions each hold a lock the other one needs, and MySQL detects the standoff and kills one of them rather than letting both wait forever. A deadlock in MySQL is expected, handled behavior β€” InnoDB is designed to detect this exact situation and resolve it automatically, but your application still needs to handle the failed transaction correctly.

The Problem

A transaction that was running fine suddenly fails with a specific error:

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

It tends to show up intermittently under concurrent load rather than consistently, which makes it harder to reproduce on demand:

SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when
trying to get lock; try restarting transaction
Two transactions, crossed lock order Txn A: locks Row 1 Txn B: locks Row 2 A wants Row 2 -> <- B wants Row 1 InnoDB detects the cycle, kills one transaction Fix: always lock rows in the same order across all code paths

Why It Happens

A deadlock occurs when transaction A holds a lock that transaction B needs, while transaction B simultaneously holds a lock that transaction A needs β€” neither can proceed, so InnoDB detects the circular wait and forcibly rolls back one of the transactions to break the cycle. Common patterns that cause this:

  • Inconsistent lock ordering β€” one code path updates rows in the order (customer, then order), while another updates the same tables in the order (order, then customer). Under concurrency, this creates exactly the crossed-lock scenario above.
  • Missing or overly broad indexes β€” without a good index, InnoDB may lock more rows than strictly necessary to satisfy a query, increasing the chance two transactions collide on rows neither actually needed to touch.
  • Long-running transactions holding locks for longer than necessary, widening the window during which a conflicting transaction can start and collide.
  • Mixing SELECT ... FOR UPDATE with plain updates on the same rows across different parts of the application in inconsistent ways.

The Fix

First, look at the actual deadlock InnoDB detected β€” it logs full details of exactly which queries and locks were involved:

SHOW ENGINE INNODB STATUS\G
------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
... holds lock on `orders` row, waiting for `customers` row
*** (2) TRANSACTION:
... holds lock on `customers` row, waiting for `orders` row
*** WE ROLL BACK TRANSACTION (1)

This tells you exactly which two operations collided, which is essential for fixing the actual ordering problem rather than guessing. The most durable fix is enforcing a consistent lock order across every code path that touches the same set of tables β€” always update in the same sequence, everywhere:

-- Consistent order: always customers, then orders, everywhere in the codebase
START TRANSACTION;
SELECT * FROM customers WHERE id = 5 FOR UPDATE;
SELECT * FROM orders WHERE customer_id = 5 FOR UPDATE;
UPDATE customers SET balance = balance - 100 WHERE id = 5;
UPDATE orders SET status = 'paid' WHERE customer_id = 5;
COMMIT;

Since deadlocks are an expected, normal occurrence under concurrency even with good design, applications should always be prepared to retry a transaction that failed with a 1213 error rather than treating it as a fatal failure:

def run_with_retry(fn, max_attempts=3):
    for attempt in range(max_attempts):
        try:
            return fn()
        except DeadlockError:
            if attempt == max_attempts - 1:
                raise
            time.sleep(0.1 * (attempt + 1))

Keep transactions as short as possible, moving any slow, non-database work outside the transaction boundary so locks are held for the minimum time necessary:

-- Bad: slow work inside the transaction
START TRANSACTION;
UPDATE orders SET status='processing' WHERE id=42;
call_slow_external_api();  -- lock held the whole time
COMMIT;

-- Better: do slow work first, then a short transaction
result = call_slow_external_api();
START TRANSACTION;
UPDATE orders SET status='processing', api_ref=result.id WHERE id=42;
COMMIT;

Still Not Working?

If deadlocks keep happening even with consistent lock ordering, check whether missing indexes are forcing InnoDB to lock more rows than intended for a given query β€” a full table scan under SELECT ... FOR UPDATE locks far more than a properly indexed query would:

EXPLAIN SELECT * FROM orders WHERE customer_id = 5 FOR UPDATE;

If the plan shows a full table scan instead of an index lookup, add an index on the filtered column, which narrows the lock footprint and reduces the odds of collision with unrelated concurrent transactions:

CREATE INDEX idx_orders_customer_id ON orders (customer_id);