Locking & concurrency
Row locks, SELECT … FOR UPDATE / FOR SHARE, NOWAIT, SKIP LOCKED, deadlocks and how to avoid them.
Isolation levels decide what a transaction sees. Locks decide what it can change and who must wait. Understanding InnoDB locking helps you avoid lost updates, double-booked seats, oversold stock and mysterious deadlocks.
How InnoDB locks#
- Row-level locks. InnoDB locks individual index records, not whole tables. Two transactions updating different rows don't block each other.
- Writes lock automatically.
UPDATE,DELETEandINSERTtake exclusive locks on the rows they touch and hold them untilCOMMITorROLLBACK. - Plain
SELECTdoesn't lock. It reads a consistent snapshot (MVCC), so readers never wait for writers. - Lock types. A shared (S) lock allows others to read-lock the row too. An exclusive (X) lock blocks all other locks on that row.
When a lock is held, other transactions wanting a conflicting lock wait, for up to innodb_lock_wait_timeout seconds (50 by default), then fail with ERROR 1205: Lock wait timeout exceeded.
The lost-update problem#
Two shoppers buy the last unit of an item at the same moment:
Plain reads don't lock, so both sessions saw 1. There are two standard fixes.
Fix 1: make the update atomic and conditional. Let the database check and change the value in one statement:
The second session's UPDATE waits for the first one's row lock, then re-checks stock >= 1 against the latest committed value and changes nothing.
Fix 2: lock the row while you decide, with a locking read.
SELECT ... FOR UPDATE and FOR SHARE#
FOR UPDATEtakes an exclusive lock on the rows read. Another transaction'sFOR UPDATE,UPDATEorDELETEon those rows waits until you commit. (Their plainSELECTs still read the snapshot without waiting.)FOR SHARE(the older spelling isLOCK IN SHARE MODE) takes a shared lock. Others can read-lock the row too, but nobody can modify it until you finish. Use it to make sure a parent row isn't deleted while you insert a child.- Locking reads always see the latest committed data, not your transaction's snapshot.
Only use locking reads inside a transaction. With autocommit, the lock is released as soon as the statement finishes.
NOWAIT and SKIP LOCKED (MySQL 8.0+)#
By default, a locking read waits. Two options change that:
NOWAIT fails immediately instead of waiting. That's useful for "someone else is editing this record" messages. SKIP LOCKED silently skips locked rows, which makes it ideal for job queues where many workers each grab a different job:
Gap locks and next-key locks#
Under REPEATABLE READ, InnoDB locks not just matching rows but also the gaps between index records in the scanned range. This stops other transactions from inserting "phantom" rows into a range you've read with a locking read:
Gap locks are why inserts sometimes wait even though no existing row conflicts. Under READ COMMITTED, InnoDB mostly turns them off, trading phantom protection for more concurrency.
Indexes matter for locking. InnoDB locks the index records it examines, not just the rows that end up changed. UPDATE jobs SET status = 'x' WHERE note = 'abc' on an unindexed column scans and locks every row, effectively locking the whole table for everyone else. Make sure the WHERE of your updates and deletes can use an index.
Deadlocks#
A deadlock happens when two transactions each hold a lock the other needs:
InnoDB detects the cycle instantly, rolls back one transaction (the victim) and lets the other continue. Deadlocks aren't bugs in MySQL; they're a normal part of concurrent systems. Your job is to make them rare and handle them:
- Retry the whole transaction on error 1213 (and 1205). Most frameworks can do this for you.
- Access rows in a consistent order. If every transfer locks the lower account id first, the cycle above can't happen.
- Keep transactions short and touch as few rows as possible.
- Use good indexes so statements lock only what they need.
- Inspect the most recent deadlock with
SHOW ENGINE INNODB STATUS\G(see the LATEST DETECTED DEADLOCK section).
Table-level locks#
You'll rarely need them with InnoDB, but you should recognise them:
LOCK TABLES t WRITE; … UNLOCK TABLES;locks whole tables. It's mostly for MyISAM and maintenance, and it implicitly commits the open transaction.- Metadata locks (MDL).
ALTER TABLEneeds an exclusive metadata lock, so it waits for every open transaction that has touched the table. One forgotten open transaction can make anALTERhang, and then every new query on that table queues up behind theALTER. CheckSHOW PROCESSLISTfor "Waiting for table metadata lock".
Seeing who is blocking whom#
(The performance_schema and sys views require the Performance Schema, which is enabled by default.)
What's next#
You can now write correct, concurrent SQL. Next you'll move logic into the database with stored procedures.
Check your understanding
Quick quiz
1.Two workers pull jobs from a queue table. Which clause lets each grab a different unlocked row without waiting?
2.What does InnoDB do when it detects a deadlock?
3.Why is an index on the WHERE column important for UPDATE statements under concurrency?
Finished reading?
Mark this lesson complete to track your progress.