Skip to main content

How to Skip Locked Rows in MySQL with SKIP LOCKED and NOWAIT

· 5 min read
Jagdish Kumawat
Founder @ Dewiride

SELECT ... FOR UPDATE waits for a row that someone else holds. Sometimes waiting is the wrong thing to do: you'd rather say "busy" right away, or take the next row instead. NOWAIT and SKIP LOCKED do those two things, and the second one turns a plain table into a job queue.

Prerequisites​

Step 1: Hold a Row​

mysql: terminal 1
START TRANSACTION;
SELECT Id, QuantityOnHand FROM products WHERE Id = 3 FOR UPDATE;

Step 2: Refuse to Wait with NOWAIT​

mysql: terminal 2
SELECT Id, QuantityOnHand FROM products WHERE Id = 3 FOR UPDATE NOWAIT;
Output
ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.

It fails immediately, instead of waiting up to 50 seconds. Your code can answer "someone else is working on this" and move on. Fixed: ERROR 3572 covers the error.

Step 3: Leave Taken Rows Out with SKIP LOCKED​

mysql: terminal 2
SELECT Id, QuantityOnHand FROM products WHERE Id IN (3, 4, 5) FOR UPDATE SKIP LOCKED;
Output
+----+----------------+
| Id | QuantityOnHand |
+----+----------------+
| 4 | 50 |
| 5 | 50 |
+----+----------------+
2 rows in set (0.002 sec)

I asked for three rows and got two. Row 3 is locked, so it's simply missing from the result, with no error and no wait. End the first transaction with ROLLBACK.

Step 4: Build a Job Queue​

A table of jobs, picked up by several workers, is where this shines. Here's a small one:

mysql
CREATE TABLE jobs (
Id int AUTO_INCREMENT PRIMARY KEY,
Name varchar(50) NOT NULL,
Status varchar(20) NOT NULL DEFAULT 'Waiting',
INDEX (Status)
);
INSERT INTO jobs (Name) VALUES ('Send invoice 1'), ('Send invoice 2'), ('Send invoice 3');

Two workers ask for the oldest waiting job at the same moment:

mysql: worker 1
START TRANSACTION;
SELECT Id, Name FROM jobs WHERE Status = 'Waiting' ORDER BY Id LIMIT 1 FOR UPDATE SKIP LOCKED;
Output (worker 1)
+----+----------------+
| Id | Name |
+----+----------------+
| 1 | Send invoice 1 |
+----+----------------+
1 row in set (0.002 sec)
mysql: worker 2
START TRANSACTION;
SELECT Id, Name FROM jobs WHERE Status = 'Waiting' ORDER BY Id LIMIT 1 FOR UPDATE SKIP LOCKED;
Output (worker 2)
+----+----------------+
| Id | Name |
+----+----------------+
| 2 | Send invoice 2 |
+----+----------------+
1 row in set (0.002 sec)

Two workers running the same SELECT with SKIP LOCKED: worker 1 gets job 1 and worker 2 gets job 2, with neither waiting.

Same statement, different jobs, and neither one waited. Each worker does its job, marks it done and commits:

mysql: worker 1
UPDATE jobs SET Status = 'Done' WHERE Id = 1;
COMMIT;
mysql: worker 2
UPDATE jobs SET Status = 'Done' WHERE Id = 2;
COMMIT;
Output (the table afterwards)
+----+----------------+---------+
| Id | Name | Status |
+----+----------------+---------+
| 1 | Send invoice 1 | Done |
| 2 | Send invoice 2 | Done |
| 3 | Send invoice 3 | Waiting |
+----+----------------+---------+
3 rows in set (0.000 sec)

If a worker crashes, its transaction rolls back, and the job goes back to being available by itself.

Which One to Use​

You wantUse
To wait your turnFOR UPDATE
An error right away, so you can tell the user it's busyFOR UPDATE NOWAIT
Whatever isn't taken, such as jobs in a queueFOR UPDATE SKIP LOCKED

MySQL's locking reads page warns: "Queries that skip locked rows return an inconsistent view of the data." That's right for a queue and wrong for a total, so never add up numbers from it.

Common Mistakes​

MistakeWhat happensDo this instead
Using SKIP LOCKED for a reportLocked rows are silently left outUse it only when any free row will do
Forgetting LIMIT 1 in a queueOne worker locks every waiting jobTake one at a time
Doing long work with the transaction openThe row stays locked, which is fine, but other locks pile up tooKeep each job's transaction small
No index on the WHERE columnThe search locks more rows than it needsIndex it

Troubleshooting​

SymptomLikely causeFix
ERROR 3572NOWAIT, and the row is lockedHandle it, or retry later
SKIP LOCKED returns nothingEvery matching row is taken, or none matchTry again later
The same job runs twiceThe worker committed before finishing, or the status wasn't updatedUpdate the status in the same transaction

FAQ​

Does it work with FOR SHARE?​

Yes. The page says: "FOR SHARE supports OF table_name, NOWAIT, and SKIP LOCKED options."

Conclusion​

Add NOWAIT to fail immediately on a locked row, and SKIP LOCKED to leave locked rows out. With ORDER BY, LIMIT 1 and SKIP LOCKED, several workers can share one table of jobs, without waiting for each other or taking the same job.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.