8
votes

Is there any way to skip "locked rows" when we make "SELECT FOR UPDATE" in MySQL with an InnoDB table?

E.g.: terminal t1

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select id from mytable ORDER BY id ASC limit 5 for update;
+-------+
| id    |
+-------+
|     1 |
|    15 |
| 30217 |
| 30218 |
| 30643 |
+-------+
5 rows in set (0.00 sec)

mysql> 

At the same time, terminal t2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select id from mytable where id>30643 order by id asc limit 2 for update;
+-------+
| id    |
+-------+
| 30939 |
| 31211 |
+-------+
2 rows in set (0.01 sec)

mysql> select id from mytable order by id asc limit 5 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> 

So if I launch a query forcing it to select other rows, it's fine.

But is there a way to skip the locked rows?

I guess this should be a redundant problem in the concurrent process, but I did not find any solution.


EDIT: In reality, my different concurrent processes are doing something apparently really simple:

  1. take the first rows (which don't contain a specific flag - e.g.: "WHERE myflag_inUse!=1").

  2. Once I get the result of my "select for update", I update the flag and commit the rows.

So I just want to select the rows which are not already locked and where myflag_inUse!=1...


The following link helps me to understand why I get the timeout, but not how to avoid it:

MySQL 'select for update' behaviour


mysql> SHOW VARIABLES LIKE "%version%";
+-------------------------+-------------------------+
| Variable_name           | Value                   |
+-------------------------+-------------------------+
| innodb_version          | 5.5.46                  |
| protocol_version        | 10                      |
| slave_type_conversions  |                         |
| version                 | 5.5.46-0ubuntu0.14.04.2 |
| version_comment         | (Ubuntu)                |
| version_compile_machine | x86_64                  |
| version_compile_os      | debian-linux-gnu        |
+-------------------------+-------------------------+
7 rows in set (0.00 sec)
5
Just don't use SELECT FOR UPDATE. Taking update locks for a long time is a bad idea and really only useful when using cursors (not a good idea either). You can't use it to emulate a checkin/checkout mechanism - Panagiotis Kanavos
@PanagiotisKanavos, I don't want to emulate this mechanism. I just want to select for update by skipping the locked rows. - Bast
Fix the other transaction so it does not take so long! - Rick James
@RickJames, what do you mean by fixing the transaction? commit/rollback?? if yes, actually I have a quite complex sql query running, and this is already taking some time... So all the concurrent requests are always waiting on the other select for update queries... - Bast
Show us the other transaction; let's discuss what can be done for it. That is, this question is "blaming the victim"; let's go after the "villain". - Rick James

5 Answers

8
votes

MySQL 8.0 introduced support for both SKIP LOCKED and NO WAIT.

SKIP LOCKED is useful for implementing a job queue (a.k.a batch queue) so that you can skip over locks that are already locked by a concurrent transaction.

NO WAIT is useful for avoiding waiting until a concurrent transaction releases the locks that we are also interested in locking.

Without NO WAIT, we either have to wait until the locks are released (at commit or release time by the transaction that currently holds the locks) or the lock acquisition times out. NO WAIT acts as a lock timeout with a value of 0.

For more details about SKIP LOCK and NO WAIT.

5
votes

This appears to now exist in MySQL starting in 8.0.1:

https://mysqlserverteam.com/mysql-8-0-1-using-skip-locked-and-nowait-to-handle-hot-rows/

Starting with MySQL 8.0.1 we are introducing the SKIP LOCKED modifier which can be used to non-deterministically read rows from a table while skipping over the rows which are locked. This can be used by our booking system to skip orders which are pending. For example:

However, I think that version is not necessarily production ready.

2
votes

Unfortunately, it seems that there is no way to skip the locked row in a select for update so far.

It would be great if we could use something like the Oracle 'FOR UPDATE SKIP LOCKED'.

In my case, the queries launched in parallel are both exactly the same, and contain a 'where' clause and a 'group by' on a several millions of rows...because the queries need between 20 and 40 seconds to run, that was (as I already knew) a big part of the problem.

The only -temporary and not the best- solution I saw was to move some (i.e.: millions of) rows that I would not (directly) use in order to reduce the time the query will take.

So I will still have the same behavior but I will wait less time...

I was expecting a way to not select the locked row in the select.

I don't mark this as an answer, so if a new clause from mysql is added (or discovered), I can accept it later...

0
votes

I'm sorry, but I think you approach the problem from a wrong angle. If your user wants to list records from a table that satisfy certain selection criteria, then your query should return them all, or return with an error message and provide no resultset whatsoever. But the query should not reurn only a subset of the results leading the user to belive that he has all the matching records.

The issue should be addressed by making sure that your application locks as few rows as possible, for as little time as possible.

0
votes

Walk through the table in chunks of the PRIMARY KEY, using some suitable LIMIT so you are not looking at "too many" rows at once.

By using the PK, you are ordering things in a predictable way; this virtually eliminates deadlocks.

By using LIMIT, you will keep from hogging too much at once. The LIMIT should be embodied as a range over the PK. This makes it quite clear if two threads are about to step on each other.

More details are (indirectly) in my blog on big deletes.