Home › Database › MySQL Lock Wait Timeout — Kill Blocker, Add Index
Advanced 5 min · September 23, 2026

MySQL Lock Wait Timeout — Kill Blocker, Add Index

Fix MySQL error 1205 by finding the blocking transaction in information_schema.innodb_trx, killing it, and adding the missing index first..

N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Written from production experience, not tutorials.

Follow
✓ Production
production tested
September 23, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 13 min
  • ✓A MySQL 5.7+ or 8.0 server with InnoDB tables
  • ✓Privilege to read information_schema and run KILL
  • ✓Comfort reading EXPLAIN output for UPDATE statements
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • Error 1205 means your transaction waited past innodb_lock_wait_timeout (default 50 seconds) for a row lock held by someone else — the query isn't slow, it's blocked
  • Find the blocker with SELECT from information_schema.innodb_trx joined to innodb_lock_waits, then end it with KILL on its thread id once you've confirmed it's idle or runaway
  • A missing index is the usual root cause: without one, InnoDB locks every row it scans, so add the index and watch lock waits collapse
  • Teach the app to retry on 1205 with backoff and keep transactions short — never hold one open across user input or HTTP calls
✦ Definition~90s read
What is MySQL Lock Wait Timeout Fix?

InnoDB protects concurrent writes with row-level locks: when a transaction updates or lock-reads a row, it holds that lock until it commits or rolls back. Any other transaction wanting the same row waits. innodb_lock_wait_timeout caps that wait — default 50 seconds — and error 1205 is what the waiter gets when the cap hits.

★
Picture a building with one restroom key shared by the whole floor.

Its sibling, error 1213 (deadlock), is different: InnoDB detects a lock cycle and kills one side immediately instead of waiting. So 1205 means someone is holding a lock for a very long time, not that two queries tangled.

The holder is frequently not doing anything at all. An idle-in-transaction session — a Django shell, a reporting tool, an app thread that forgot COMMIT — holds every lock it ever took while it sits there. One abandoned session can block hundreds of checkouts.

The query information_schema.innodb_trx exposes every open transaction with its start time, state, thread id, and current SQL, and the innodb_lock_waits table maps each waiter to its blocker, so diagnosis is two queries, not guesswork.

The deeper cause behind most pile-ups is a missing index. Without a usable index, an UPDATE ... WHERE status='paid' scans and locks every row it examines — effectively a table lock — while with the index it locks only matching rows. That's why the durable fix for recurring 1205s is almost always CREATE INDEX, not a bigger timeout.

Raising the timeout just converts a loud 50-second failure into a silent 5-minute stall that exhausts your connection pool first.

Plain-English First

Picture a building with one restroom key shared by the whole floor. Your query stands at the door waiting for the key, and after 50 seconds MySQL gives up with error 1205 — the lock wait timeout. The fix has three parts: find who's holding the key (the blocking transaction), take it back if they're gone (KILL), and ask why everyone needs the same restroom (usually a missing index). Build more restrooms and the line disappears.

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction. It lands mid-incident, usually during a sale, a batch job, or a migration — dozens of app threads stuck, all showing the same error, none of them actually slow. Your first instinct is to blame the query, add a bigger timeout, and move on. That instinct is wrong often enough to cost real money.

Error 1205 is a waiting-room problem, not a speed problem. Your transaction asked InnoDB for a row lock, another transaction held it, and yours waited the full innodb_lock_wait_timeout (50 seconds by default) before giving up. The query would have finished in milliseconds if the lock had been free.

This guide shows the full playbook: confirming it's a lock wait and not a deadlock, finding the exact blocking transaction with information_schema, killing it safely, fixing the missing index that usually caused the pile-up, and adding retry discipline so one stuck session never pages you again. Keep this page open during the incident — the two diagnostic queries in section two resolve most 1205s in under five minutes.

Error 1205 Is a Blocked Query, Not a Slow Query

Read the message literally: lock wait timeout exceeded means your transaction spent the whole timeout waiting for a lock, then gave up. The query itself might run in 3 milliseconds on an idle server — you'll never see it in the slow log, the CPU stays flat, and EXPLAIN looks innocent. That's the signature that separates 1205 from genuine slowness: system metrics look bored while app threads stack up.

Its close relative is error 1213, deadlock found. A deadlock is a cycle — A waits on B waits on A — which InnoDB detects in about a second and resolves by killing one side. A lock wait has no cycle, just a holder that won't let go, so InnoDB waits the full timeout. If you see 1213s, you reorder work or shorten transactions; if you see 1205s, you find the holder. Mixing up the two sends you optimizing a query plan when you should be hunting a session.

Start every 1205 incident by confirming the timeout value and InnoDB's own status output. SELECT @@GLOBAL.innodb_lock_wait_timeout shows the cap everyone waits under, and SHOW ENGINE INNODB STATUS names the current waiters and holders. Those two outputs frame the whole investigation: how long waiters suffer, and who is doing the holding.

confirm_1205.sqlSQL
1
2
3
4
5
6
7
8
9
-- The cap every waiter suffers under (default 50 seconds)
SELECT @@GLOBAL.innodb_lock_wait_timeout;
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

-- InnoDB's own view: TRANSACTIONS section names waiters + holders
SHOW ENGINE INNODB STATUS;

-- 1205 = waited and gave up; 1213 = deadlock, InnoDB killed one side
-- Flat CPU + stacked app threads + 1205s = blocked, not slow.
📊 Production Insight
A team tuned a checkout query for two days before noticing CPU was at 22% — the query ran in 4 ms unblocked. The real holder was an idle console found in one innodb_trx query.
🎯 Key Takeaway
Treat 1205 as a waiting-room problem: confirm the timeout, read InnoDB status, and hunt the lock holder instead of tuning the query.

Find the Blocker With innodb_trx and innodb_lock_waits

Two performance_schema-adjacent tables in information_schema carry the whole story. innodb_trx lists every open InnoDB transaction: its id, state, start time, thread id, and current statement. Ordering by trx_started puts the likeliest blockers — the oldest transactions — at the top. A row whose trx_query is NULL but whose state isn't COMMITTING is idle-in-transaction: connected, holding locks, doing nothing.

innodb_lock_waits completes the picture by pairing each waiting transaction with the one blocking it. Join it to innodb_trx on the blocking id and you get the blocker's thread id and SQL in a single result set — no log archaeology, no guessing from timestamps. During an incident, run the pair of queries back to back: the age-ordered transaction list for context, the waits join for the precise edge.

Save both outputs into the incident ticket. Blocker thread ids get reused, so 'we killed 48213' is meaningless tomorrow without the query text and start time attached. The habit that separates fast teams here is screenshotting the waits join within the first five minutes, before anyone kills anything and destroys the evidence. During a real pile-up, run the waits join first — it names the single thread to act on while the age-ordered list loads context around it.

find_blocker.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- All open transactions, oldest first (blockers float to the top)
SELECT trx_id, trx_state, trx_started,
  TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_s,
  trx_mysql_thread_id, trx_rows_modified, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started;

-- Exact waiter -> blocker edges with the blocker's thread + SQL
SELECT w.requesting_trx_id AS waiter,
  w.blocking_trx_id AS blocker,
  t.trx_mysql_thread_id AS blocker_thread,
  t.trx_query AS blocker_query
FROM information_schema.innodb_lock_waits w
JOIN information_schema.innodb_trx t ON t.trx_id = w.blocking_trx_id;
📊 Production Insight
One joins query named thread 48213 — an admin console idle for 26 minutes — while 90 app threads waited on it. The whole diagnosis took under a minute once someone queried the waits table.
🎯 Key Takeaway
Age-ordered innodb_trx plus the innodb_lock_waits join names the blocker precisely — capture both before killing anything.

KILL the Blocker Without Making Things Worse

KILL in MySQL takes a thread id — the trx_mysql_thread_id from your blocker query — and rolls back that thread's open transaction. For an idle-in-transaction console holding locks with zero uncommitted changes, the rollback is instant and checkouts recover in seconds. For a thread mid-way through a 2-million-row batch, the rollback itself can take minutes and hold worse locks while it unwinds. Always check trx_rows_modified before you pull the trigger, and prefer killing during a lull if the blocker is a legitimate batch you can rerun.

Use KILL <thread_id> (statement kill) rather than KILL CONNECTION when the thread belongs to your app pool — severing pooled connections creates reconnect storms on top of the lock incident. After the kill, re-run the innodb_trx query to confirm the thread is gone; a KILLED flag that lingers means rollback is still unwinding, and patience beats a second kill.

Treat every kill as a follow-up ticket, not a resolution. The kill restores service; the ticket asks why a 26-minute idle transaction was possible and adds the guardrail — idle timeouts on admin tools, statement timeouts on reports, autocommit defaults — so you never kill the same class of blocker twice.

kill_blocker.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
-- Check rollback cost BEFORE killing (big = slow unwind)
SELECT trx_mysql_thread_id, trx_rows_modified, trx_query
FROM information_schema.innodb_trx
WHERE trx_mysql_thread_id = 48213;

-- End the blocker (rolls back its open transaction)
KILL 48213;

-- Confirm it's actually gone (lingering KILLED = rollback unwinding)
SELECT trx_id, trx_state, trx_mysql_thread_id
FROM information_schema.innodb_trx
WHERE trx_mysql_thread_id = 48213;
⚠ Check trx_rows_modified Before You KILL
Killing a thread with 2 million uncommitted row changes triggers a long rollback that can stall worse than the original wait. Kill idle sessions freely; schedule kills of big batches for a lull.
📊 Production Insight
An engineer killed a migration thread holding 1.8M row changes at peak — the 6-minute rollback hurt more than the wait. Now the runbook checks trx_rows_modified first.
🎯 Key Takeaway
KILL the thread id after weighing rollback cost, confirm it vanished, and ticket the guardrail that prevents the next one.

The Missing Index That Turned One Row Into a Table Lock

Here's why 1205s recur without any idle villain. An UPDATE ... WHERE status = 'paid' with no index on status must examine every row, and InnoDB locks the rows it examines — so a one-row change briefly locks the whole table. Under light traffic nobody notices; under sale traffic every checkout queues behind every other checkout, and the 50-second cap starts firing across the fleet.

EXPLAIN is the confirmation: type ALL with rows in the tens of thousands on a write statement means a table-wide lock footprint. The fix is an index on the filter columns, which narrows both the scan and the lock set to matching rows. After CREATE INDEX, re-run EXPLAIN to confirm the ref or range access, then watch the lock-wait counters fall off a cliff.

Mind the online-DDL cost on the fix itself: building an index on a 100M-row table locks more than you'd like on older MySQL, so use ALGORITHM=INPLACE where supported or an online schema-change tool during a quiet window. And index foreign-key columns as a standing rule — cascading child-table checks without indexes are the other classic full-scan lock source that pages teams at 3 AM. After the index lands, confirm the win with SHOW GLOBAL STATUS LIKE 'Innodb_row_lock_waits' — the counter should stop climbing as throughput recovers.

index_lock_fix.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
-- Prove the table-wide lock footprint first
EXPLAIN UPDATE orders SET status = 'paid' WHERE status = 'new' AND id = 881;

-- Narrow the scan AND the lock set to matching rows only
CREATE INDEX idx_orders_status ON orders (status);

-- Verify the plan changed from ALL to ref/range
EXPLAIN UPDATE orders SET status = 'paid' WHERE status = 'new' AND id = 881;

-- Online build on huge tables (avoid blocking writes while fixing)
-- ALTER TABLE orders ADD INDEX idx_orders_status (status), ALGORITHM=INPLACE, LOCK=NONE;
📊 Production Insight
One index on orders(status) cut rows examined per checkout from 41,000 to 3 — and the 1205s stopped the same minute, with no timeout change at all.
🎯 Key Takeaway
Unindexed writes lock everything they scan: EXPLAIN the waiter, index the filter, and the pile-ups end at the source.

Retry Discipline: Teach the App to Survive 1205

Even healthy systems hit transient lock waits — two checkouts racing for the last item, a deploy restarting workers mid-transaction. The app should treat 1205 (and 1213) as retryable: catch the error code, back off with jitter, and replay the transaction a bounded number of times. Three attempts with 50–200 ms jittered delays absorb nearly all benign collisions without amplifying a real incident the way unbounded retries do. Cap it: infinite retry on a genuine 26-minute blocker is a self-inflicted DDoS.

Keep transactions short enough that retries stay cheap. Fetch everything you need, then open the transaction, write, and commit immediately — never hold one open across user input, HTTP calls, or queue publishes. Each statement inside the transaction should touch indexed rows so the lock window is milliseconds, and interactive paths can set a shorter session timeout so they fail fast instead of camping in the pool.

Make retries idempotent or don't retry at all. A checkout replay must not charge twice: gate the write on a unique idempotency key or a state precondition (WHERE status='new') so a replayed commit is a harmless no-op. Retry without idempotency converts a locking incident into a double-charge incident, which is strictly worse.

retry_safe_txn.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
-- Fail fast on interactive paths instead of camping 50s in the pool
SET SESSION innodb_lock_wait_timeout = 10;

-- Short, idempotent transaction: precondition makes replays safe
START TRANSACTION;
UPDATE orders SET status = 'paid'
WHERE id = 881 AND status = 'new';
-- app checks affected-rows: 0 = already paid (safe replay), 1 = just paid
COMMIT;

-- Retryable errors the app should catch with capped backoff + jitter:
-- ERROR 1205 (lock wait timeout), ERROR 1213 (deadlock).
💡Retry Only What's Idempotent
Gate retried writes on a unique key or state precondition (WHERE status='new') so a replay can't double-charge. Bounded retries plus idempotency absorb blips; unbounded retries amplify real incidents.
📊 Production Insight
Adding three capped, jittered retries on 1205/1213 cut checkout paging by 80% — the remaining pages were genuine blockers worth waking someone for.
🎯 Key Takeaway
Catch 1205/1213 with bounded jittered retries, keep transactions short, and gate replays on idempotency preconditions.

Prevention: Short Transactions, Alerts, and Reviews

The lasting fix is observability plus habits. Alert on transaction age, not query time: any transaction open longer than 60 seconds deserves a page, because the damage scales with age, not with what the SQL says. A five-line cron check against innodb_trx feeding your alerting catches idle consoles, forgotten shells, and runaway reports before the sale starts — not during it.

Review every write path for its lock footprint before it ships. EXPLAIN each UPDATE and DELETE in the migration PR, require indexes on filter and foreign-key columns, and flag transactions that span multiple statements or service calls. Most 1205-prone code is visible in review: a loop that updates rows one by one inside a single transaction, or a handler that calls a payment API between BEGIN and COMMIT.

Finally, give humans safe defaults. Admin consoles and notebooks should run with autocommit on and an idle-session timeout, reporting jobs get statement timeouts and read-only replicas, and deploy pipelines verify no transaction older than a minute exists before shifting traffic. Boring guardrails, zero 3 AM pages — that's the trade you're buying. Put the transaction-age check on the same dashboard as pool saturation: the two graphs spike together, and either one alone can mislead you.

watch_long_trx.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Cron every minute: page on any transaction open > 60s
#!/bin/bash
set -euo pipefail
COUNT=$(mysql -u monitor -h db-primary -N -e "
SELECT COUNT(*) FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;")
if [ "$COUNT" -gt 0 ]; then
  mysql -u monitor -h db-primary -e "
  SELECT trx_mysql_thread_id, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_s,
  trx_query FROM information_schema.innodb_trx
  WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;"
  echo "PAGE: $COUNT long-running transactions"
  exit 1
fi
echo "OK: no long transactions"
📊 Production Insight
A one-minute cron on innodb_trx age now pages before sales begin — the team hasn't had a lock-wait incident reach customers in eight months.
🎯 Key Takeaway
Alert on transaction age over 60s, review write lock footprints in PRs, and default human tools to autocommit with idle timeouts.
● Production incidentPOST-MORTEMseverity: high

A Forgotten Admin Session Blocked 1,140 Checkouts in 12 Minutes

Symptom
At 7:02 PM, two minutes into a flash sale, checkout errors jumped from zero to 95 per minute — every one ERROR 1205 on UPDATE orders SET status='paid'. The product pages were fast, the database CPU sat at 22%, and slow-query logs showed nothing over a second. By 7:14 PM, 1,140 payments had failed and the queue of stuck app threads had exhausted half the Hikari pool, turning a lock problem into a connection problem on top.
Assumption
The team assumed the sale traffic had overwhelmed the primary, so they failed over to the replica topology and raised innodb_lock_wait_timeout from 50 to 200 seconds. Failover changed nothing, and the longer timeout made things worse: threads now hung for 200 seconds instead of failing fast, so the pool drained completely and even read endpoints started timing out.
Root cause
Two compounding causes. First, a support engineer had opened a transaction in an admin console at 6:36 PM to inspect a disputed order and never committed — it sat idle-in-transaction holding locks on hot order rows for 26 minutes. Second, the UPDATE ... WHERE status filter had no index, so each checkout locked far more rows than its own, multiplying the blast radius of that one idle session. information_schema.innodb_lock_waits pointed at thread 48213 in a single query once someone looked.
Fix
They ran KILL 48213 on the idle thread (it rolled back one uncommitted inspection row — harmless), and checkouts recovered within 30 seconds. Then they added the missing index with CREATE INDEX idx_orders_status ON orders(status), which cut rows examined per checkout from 41,000 to 3. The timeout was reverted to 50. Finally, a cron check now pages when any transaction stays open longer than 60 seconds, and the admin console got autocommit-on with a 5-minute idle kill.
Key lesson
  • Query the lock tables before touching timeouts: one SELECT on innodb_trx and innodb_lock_waits names the blocker in seconds, while raising the timeout converts fast failures into pool-draining stalls.
  • Idle-in-transaction sessions are blockers too: alert on transaction age, not just query time, and give admin tools autocommit plus idle timeouts by default.
  • Chase the lock footprint, not the traffic: when every thread fails on the same statement, EXPLAIN it and index the filter — the pile-up is usually one missing index wide.
Production debug guideSix checks that go from symptom to named blocker to root cause without restarting anything.6 entries
Symptom · 01
Many threads report ERROR 1205 on the same statement at once
→
Fix
Confirm it's a lock wait and capture InnoDB's view: SHOW VARIABLES LIKE 'innodb_lock_wait_timeout'; then SHOW ENGINE INNODB STATUS; and read the TRANSACTIONS section for the waiting and blocking thread ids. If the message were a deadlock (1213), InnoDB would have picked a victim already — 1205 means something is simply sitting on the lock, so hunt the holder, not the query plan, first.
Symptom · 02
You need every open transaction with its age and thread id
→
Fix
Run SELECT trx_id, trx_state, trx_started, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_s, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx ORDER BY trx_started; Any row with age_s in the hundreds and trx_state like LOCK WAIT or RUNNING-but-idle is your suspect list. Oldest first — the blocker is usually the oldest transaction touching that table.
Symptom · 03
You need the exact waiter-to-blocker mapping
→
Fix
Join the waits table: SELECT w.requesting_trx_id AS waiter, w.blocking_trx_id AS blocker, t.trx_mysql_thread_id AS blocker_thread, t.trx_query AS blocker_query FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx t ON t.trx_id = w.blocking_trx_id; That blocker_thread is the id you pass to KILL, and blocker_query tells you whether it's a report, a migration, or an idle console.
Symptom · 04
Blocker identified — end it with minimum blast radius
→
Fix
If the blocker's trx_query is NULL or an innocent SELECT, it's idle-in-transaction: run KILL 48213; (the thread id, no CONNECTION keyword needed) and re-run the innodb_trx query to confirm it's gone. Expect a brief rollback stall proportional to what it had changed — check trx_rows_modified first so a 2-million-row rollback doesn't surprise you during peak.
Symptom · 05
Same statement 1205s daily even with no idle sessions around
→
Fix
Stop raising the timeout and inspect the lock footprint: SHOW VARIABLES LIKE 'innodb_lock_wait_timeout'; to confirm you're at the default 50, then EXPLAIN the waiting UPDATE/DELETE. A full-table EXPLAIN with no usable key means every execution locks the whole table — the fix is an index on the filter columns, which shrinks each transaction's lock set from everything to just its rows.
Symptom · 06
App threads pile up behind each 1205 instead of recovering
→
Fix
Check the pool during the incident: SHOW PROCESSLIST; (or PERFORMANCE_SCHEMA threads) and count threads stuck past 30 seconds. If the pool is saturated with waiters, fail fast instead of waiting: SET SESSION innodb_lock_wait_timeout = 10; for interactive paths, and add bounded retries with jitter in the app so a 10-second blip resolves itself instead of queuing hundreds of doomed threads.
MySQL Lock Wait Causes at a Glance
Root CauseHow to ConfirmFixPrevention
Idle-in-transaction session holding locksinnodb_trx shows old trx with NULL query; waits join points at itKILL the thread; confirm rollback finishedAutocommit + idle timeouts on consoles; age alerts
Long report or migration on hot rowsBlocker query is a big SELECT FOR UPDATE or batch writeKILL or let finish off-peak; rerun on replicaRoute reports to replicas; chunk batches with commits
Missing index causing table-wide locksEXPLAIN shows type ALL on the waiting writeCREATE INDEX on filter/FK columnsRequire EXPLAIN + indexes in migration review
Timeout set too low for legit workBlockers are short healthy trx; waits barely exceed capRaise SESSION timeout for that job onlyKeep GLOBAL at 50; tune per-session, not globally
App pile-up with no retry disciplinePool saturated with waiters; same 1205 across fleetFail fast + bounded jittered retriesShort idempotent transactions; cap pool waiters
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
confirm_1205.sqlSELECT @@GLOBAL.innodb_lock_wait_timeout;Error 1205 Is a Blocked Query, Not a Slow Query
find_blocker.sqlSELECT trx_id, trx_state, trx_started,Find the Blocker With innodb_trx and innodb_lock_waits
kill_blocker.sqlSELECT trx_mysql_thread_id, trx_rows_modified, trx_queryKILL the Blocker Without Making Things Worse
index_lock_fix.sqlEXPLAIN UPDATE orders SET status = 'paid' WHERE status = 'new' AND id = 881;The Missing Index That Turned One Row Into a Table Lock
retry_safe_txn.sqlSET SESSION innodb_lock_wait_timeout = 10;Retry Discipline
watch_long_trx.shset -euo pipefailPrevention

Key takeaways

1
Error 1205 means blocked, not slow
flat CPU with stacked threads is the signature.
2
Name the blocker with innodb_trx plus the innodb_lock_waits join before changing anything.
3
Check trx_rows_modified, then KILL the thread id
and confirm the rollback finished.
4
Unindexed writes lock everything they scan
EXPLAIN the waiter and index the filter.
5
Retry 1205/1213 with bounded jittered attempts gated on idempotency preconditions.
6
Alert on transaction age over 60 seconds so idle holders page before peak traffic.

Common mistakes to avoid

5 patterns
×

Raising innodb_lock_wait_timeout instead of finding the blocker

Symptom
1205s become 200-second hangs; the pool drains and reads start failing too.
Fix
Keep GLOBAL at 50, query the waits join for the holder, and fix the footprint — tune timeout per-session only.
×

Using KILL CONNECTION on pooled app threads

Symptom
Lock clears but the pool erupts in reconnect storms and fresh 1205s.
Fix
Use statement KILL on the thread id; reserve KILL CONNECTION for truly wedged interactive sessions.
×

Leaving foreign-key and status columns unindexed

Symptom
Every write locks the full table; traffic that fit yesterday 1205s today.
Fix
Index FK columns and write filters as a standing rule; verify with EXPLAIN before merging migrations.
×

Disabling autocommit in tools and forgetting COMMIT

Symptom
An inspection query at 6 PM blocks the 7 PM sale with zero CPU signal.
Fix
Default consoles to autocommit; add idle-transaction timeouts and age-based paging.
×

Retrying 1205s instantly with no backoff or idempotency

Symptom
Retry storms multiply load during real blockers; replays double-charge customers.
Fix
Bound attempts (3), jitter delays, and gate replays on idempotency keys or state preconditions.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does MySQL error 1205 mean, and how is it different from 1213?
Q02SENIOR
How do you find which transaction is blocking a 1205 waiter?
Q03SENIOR
Why does a missing index turn a one-row update into a table lock?
Q04SENIOR
KILL vs KILL CONNECTION — which do you use on a blocking pool thread, an...
Q05SENIOR
Design checkout code that survives transient 1205s without double-chargi...
Q01 of 05JUNIOR

What does MySQL error 1205 mean, and how is it different from 1213?

ANSWER
1205 means a transaction waited past innodb_lock_wait_timeout for a lock held by someone else — a slow holder. 1213 is a deadlock cycle that InnoDB detects and resolves within a second by killing one side. Debug 1205 by finding the holder; debug 1213 by reordering work.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Should I just raise innodb_lock_wait_timeout to stop 1205s?
02
How do I tell a lock wait from a deadlock?
03
Is it safe to KILL a blocking thread in production?
04
Why did 1205s start when traffic barely grew?
05
Do SELECTs cause lock waits too?
06
What belongs in a deploy gate for this?
N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Written from production experience, not tutorials.

Follow
✓ Verified
production tested
September 23, 2026
last updated
1,942
articles · all by Naren
🔥

That's MySQL. Mark it forged?

5 min read · try the examples if you haven't

←
Previous
MySQL 1045 Access Denied Fix
2 / 7 · MySQL
Next
MySQL Incorrect String Value Fix
→