how does mysql select for update works [duplicate] - mysql

This is not a full/correct MySQL query only pseudo-code:
Select *
from Notifications as n
where n.date > (CurrentDate-10 days)
limit by 1
FOR UPDATE
http://dev.mysql.com/doc/refman/5.0/en/select.html states:
If you use FOR UPDATE with a storage engine that uses page or row locks, rows examined by the query are write-locked until the end of the current transaction
Is here only the one record returned locked by MySQL or all records it has to scan to find the single record?

Why don't we just try it?
Set up the database
CREATE DATABASE so1;
USE so1;
CREATE TABLE notification (`id` BIGINT(20), `date` DATE, `text` TEXT) ENGINE=InnoDB;
INSERT INTO notification(id, `date`, `text`) values (1, '2011-05-01', 'Notification 1');
INSERT INTO notification(id, `date`, `text`) values (2, '2011-05-02', 'Notification 2');
INSERT INTO notification(id, `date`, `text`) values (3, '2011-05-03', 'Notification 3');
INSERT INTO notification(id, `date`, `text`) values (4, '2011-05-04', 'Notification 4');
INSERT INTO notification(id, `date`, `text`) values (5, '2011-05-05', 'Notification 5');
Now, start two database connections
Connection 1
BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;
Connection 2
BEGIN;
If MySQL locks all rows, the following statement would block. If it only locks the rows it returns, it shouldn't block.
SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;
And indeed it does block.
Interestingly, we also cannot add records that would be read, i.e.
INSERT INTO notification(id, `date`, `text`) values (6, '2011-05-06', 'Notification 6');
blocks as well!
I can't be sure at this point whether MySQL just goes ahead and locks the entire table when a certain percentage of rows are locked, or where it's actually really intelligent in making sure the result of the SELECT ... FOR UPDATE query can never be changed by another transaction (with an INSERT, UPDATE, or DELETE) while the lock is being held.

The thread is pretty old, just to share my two cents regarding the tests above performed by #Frans
Connection 1
BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;
Connection 2
BEGIN;
SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;
The concurrent transaction 2 will be blocked for sure, but the reason is NOT that the transaction 1 is holding the lock on the whole table. The following explains what has happened behind the scene:
First of all, the default isolation level of the InnoDB storage engine is Repeatable Read. In this case,
1- When the column used in where condition is not indexed (as the case above):
The engine is obliged to perform a full table scan to filter out the records not matching the criteria. EVERY ROW that have been scanned are locked in the first place. MySQL may release the locks on those records not matching the where clause later on. It is an optimization for the performance, however, such behavior violates the 2PL constraint.
When transaction 2 starts, as explained, it needs to acquire the X lock for each row retrieved although there exists only a single record (id = 2) matching the where clause. Eventually the transaction 2 will be waiting for the X lock of the first row (id = 1) until the transaction 1 commits or rollbacks.
2- When the column used in where condition is a primary index
Only the index entry satisfying the criteria is locked. That's why in the comments someone says that some tests are not blocked.
3 - When the column used in where condition is an index but not unique
This case is more complicated. 1) The index entry is locked. 2) One X lock is attached to the corresponding primary index. 3) Two gap locks are attached to the non-existing entries right before and after the record matching the search criteria.

I know this question is pretty old, but I've wanted to share the results of some relevant testing I've done with indexed columns which has yielded some pretty strange results.
Table structure:
CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`notid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
12 rows inserted with INSERT INTO t1 (notid) VALUES (1), (2),..., (12). On connection 1:
BEGIN;
SELECT * FROM t1 WHERE id=5 FOR UPDATE;
On connection 2, the following statements are blocked:
SELECT * FROM t1 WHERE id!=5 FOR UPDATE;
SELECT * FROM t1 WHERE id<5 FOR UPDATE;
SELECT * FROM t1 WHERE notid!=5 FOR UPDATE;
SELECT * FROM t1 WHERE notid<5 FOR UPDATE;
SELECT * FROM t1 WHERE id<=4 FOR UPDATE;
The strangest part is that SELECT * FROM t1 WHERE id>5 FOR UPDATE; is not blocked, nor are any of
...
SELECT * FROM t1 WHERE id=3 FOR UPDATE;
SELECT * FROM t1 WHERE id=4 FOR UPDATE;
SELECT * FROM t1 WHERE id=6 FOR UPDATE;
SELECT * FROM t1 WHERE id=7 FOR UPDATE;
...
I'd also like to point out that it seems the entire table is locked when the WHERE condition in the query from connection 1 matches a non-indexed row. For example, when connection 1 executes SELECT * FROM t1 WHERE notid=5 FOR UPDATE, all select queries with FOR UPDATE and UPDATE queries from connection 2 are blocked.
-EDIT-
This is a rather specific situation, but it was the only I could find that exhibits this behaviour:
Connection 1:
BEGIN;
SELECT *, #x:=#x+id AS counter FROM t1 CROSS JOIN (SELECT #x:=0) b HAVING counter>5 LIMIT 1 FOR UPDATE;
+----+-------+-------+---------+
| id | notid | #x:=0 | counter |
+----+-------+-------+---------+
| 3 | 3 | 0 | 9 |
+----+-------+-------+---------+
1 row in set (0.00 sec)
From connection 2:
SELECT * FROM t1 WHERE id=2 FOR UPDATE; is blocked;
SELECT * FROM t1 WHERE id=4 FOR UPDATE; is not blocked.

Following links from the documentation page you posted gives more information about locking. In this page
A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.
This seems pretty clear that it is all rows that it has to scan.

From mysql official doc:
A locking read, an UPDATE, or a DELETE generally set record locks on every index record that is scanned in the processing of the SQL statement. It does not matter whether there are WHERE conditions in the statement that would exclude the row.
For the case discussed in Frans' answer, all rows are locked because there's a table scan during sql processing:
If you have no indexes suitable for your statement and MySQL must scan the entire table to process the statement, every row of the table becomes locked, which in turn blocks all inserts by other users to the table. It is important to create good indexes so that your queries do not unnecessarily scan many rows.
Check the latest doc here: https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html

As others have mentioned, SELECT... FOR UPDATE locks all rows encountered in the default isolation level. Try setting the isolation for the session which runs this query to READ COMMITTED, for example precede the query with: set session transaction isolation level read committed;

It locks all the rows selected by query.

Related

select for update twice to same table by diffrenet key cause deadlock in MySQL

Table foo has id and name.
Transaction A select for update by id 1.
Transaction B select for update by id 1 then wait.
Transaction A select for update by name anything(even though not exists) cause Transaction B deadlock.
Why this happen ?
Below scripts reproduce deadlock.
create table foo (id int primary key, name varchar(100));
insert into foo values (1, 'foo1');
-- transaction A
start transaction;
select * from foo where id=1 for update;
-- transaction B
start transaction;
select * from foo where id=1 for update;
-- now waiting
-- transaction A
select * from foo where name='xxxxx' for update;
-- transaction B dead lock occer
I figured out close answer.
MySQL locks all records when select for update searched by no indexed column.
https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html
If you have no indexes suitable for your statement and MySQL must scan the entire table to process the statement, every row of the table becomes locked, which in turn blocks all inserts by other users to the table.
But, I don't know why this causes deadlock.
By the way, I resolved my problem that every select for update to search by primary key.

MySQL : Can I use one SELECT ... FOR UPDATE to "protect" multiple tables? ( LOCKING )

I'm reading the MySQL docs for hours but I still cannot answer to myself a couple of pretty simple questions... :(
Here is my (simplified) scenario: I have two tables in a database: tablea and tableb, both tables use the InnoDB storage engine. tablea (which is my main table) has a PRIMARY index (id) with autoincrement. Now here is what I want to achieve and please keep in mind that the following business logic can be and will be run concurrently:
I start a transaction:
START TRANSACTION
BEGIN
then I check if an id exists in tablea if yes, I SELECT the row FOR UPDATE, let's call the id I am looking for myid :
SELECT `id` FROM `tablea` WHERE `id`='myid' FOR UPDATE;
if the above SELECT returns no rows, I simply ROLLBACK the transaction and exit from my function. In other words I'm done when myid is not present in tablea.
On the other hand when myid exists then first I need to update some values in tablea:
UPDATE `tablea` SET `somefield`='somevalue' WHERE `id`='myid';
then I need to check if myid also exists in tableb:
SELECT * FROM `tableb` WHERE `id`='myid' FOR UPDATE;
my first question is about the above SELECT statement: Is it okay to do another SELECT FOR UPDATE here (on tableb) ??? Or "FOR UPDATE" is not needed here when dealing with tableb, because I already started a transaction and also acquired a lock based on a row in tablea ??? Can someone please answer this?
The last SELECT statement above either returns a row from tableb (and locks that row for update) or it turns out that myid does not exist in tableb.
When myid is present in tableb then I just need to update some values in that row, it's simple:
UPDATE `tableb` SET `somefieldintableb`='somevaluefortableb' WHERE `id`='myid';
On the other hand when myid is not in tableb I need to insert it, and here comes my 2nd question: Should I lock tableb before I issue my INSERT INTO statement, like this:
LOCK TABLES `tableb` WRITE;
INSERT INTO `tableb` (`id`,`somefieldintableb`) VALUES ('myid','somevaluefortableb');
UNLOCK TABLES `tableb`;
and then finally, I do:
COMMIT
My goal is this: Since the above described function (with the MySQL transaction) will run in many instances in parallel, I want to prevent any of those instances updating the same row in either tablea or tableb at the same time. I also want to prevent double-insertion of myid into tableb, hence I thought about using LOCK TABLES when myid was not found in tableb.
So I have two questions: Should I do a SELECT ... FOR UPDATE within my already started transaction when I want to update tableb or locking tableb with SELECT ... FOR UPDATE is unnecessary, because holding the lock on tablea already "protects" tableb too from simultaneous UPDATEs in this case ??? Thanks to the way I started my transaction, I mean.
2nd question: When I need to INSERT a new row into tableb should I lock the whole table for that insertion? Or is that something that is totally unnecessary in this case? (Do I need LOCK TABLES tableb or not?)
I would appreciate if an expert can answer these two questions for me, because reading the various docs and examples online simply won't help me answering these questions. :(
I would do it this way:
BEGIN;
SELECT a.`id` AS a_id, b.`id` AS b_id
FROM `tablea` AS a LEFT OUTER JOIN `tableb` AS b ON a.id=b.id
WHERE a`id`='myid'
FOR UPDATE;
Now you have row locks on both tablea and tableb if rows exist. If the SELECT returns nothing, you know the id is not present in tablea. If the SELECT returns a row with a value for a_id, but a NULL for b_id, then you know it's present in tablea and not in tableb.
If the row is present in both tables, this locks rows in both tables simultaneously. If you do it in two steps, you might risk a race condition and deadlock.
Try the INSERT and use ON DUPLICATE KEY UPDATE:
INSERT INTO `tableb` (id, somefieldintableb) VALUES ('myid', 'somevaluefortableb')
ON DUPLICATE KEY UPDATE `somefieldintableb`='somevaluefortableb';
If the row with your desired id value is not present, this will insert it. If the row is present, this will update the row. And you're sure to have access to an existing row, because your SELECT FOR UPDATE locked it earlier.
Don't use table locks if you can avoid it. That's a sure way to create a bottleneck in your application.
Re your comments:
Yes, you can use extra join conditions for the date column.
You don't have to update all the columns when you use ON DUPLICATE KEY UPDATE. You can leave most of them alone if the row exists, and just update one, or a few, or whatever.
Also you can reference the value you tried to insert.
INSERT INTO `tableb` (id, date, col1, col2, col3, col4, col5, col6)
VALUES ('myid', $a_date, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE col4=VALUES(col4);
For more details, I recommend reading http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

could a rows lock made with IN(,,,) generate dead locks?

My goal is to avoid dead locks and so I centralized all locks in the same place ordering by table name and then by ID ascending:
SELECT * FROM table1 WHERE ID = 1 FOR UPDATE
SELECT * FROM table1 WHERE ID = 2 FOR UPDATE
SELECT * FROM table1 WHERE ID = 3 FOR UPDATE
SELECT * FROM table1 WHERE ID = 4 FOR UPDATE
SELECT * FROM table2 WHERE ID = 1 FOR UPDATE
SELECT * FROM table2 WHERE ID = 2 FOR UPDATE
SELECT * FROM table2 WHERE ID = 3 FOR UPDATE
SELECT * FROM table2 WHERE ID = 4 FOR UPDATE
but I wonder if I can do the same using IN() (which is probably a bit faster)
SELECT * FROM table1 WHERE ID IN(1,2,3,4) FOR UPDATE
SELECT * FROM table2 WHERE ID IN(1,2,3,4) FOR UPDATE
will the rows be locked in the exact order specified by the IN() operand or the lock will be applied using the "natural table ordering" instead?
ID is a primary auto_increment field in all tables and I don't "reuse" old deleted IDs (so in theory the natural ordering should always be ascending)
thanks in advance!
added the update:
UPDATE table1 SET t1="hello1" WHERE ID = 1;
UPDATE table1 SET t1="hello2" WHERE ID = 2;
UPDATE table1 SET t1="hello3" WHERE ID = 3;
UPDATE table1 SET t1="hello4" WHERE ID = 4;
UPDATE table2 SET t2="hello1" WHERE ID = 1;
UPDATE table2 SET t2="hello2" WHERE ID = 2;
UPDATE table2 SET t2="hello3" WHERE ID = 3;
UPDATE table2 SET t2="hello4" WHERE ID = 4;
...
COMMIT;
Even though it's a little unclear but some part of the answer to your question is stated in the MySQL's documentation:
expr IN (value,...)
Returns 1 if expr is equal to any of the values in the IN list, else
returns 0. If all values are constants, they are evaluated according
to the type of expr and sorted. The search for the item then is done
using a binary search.
Here's what you should get of it: If all values in the list are constants, they are compared sorted using binary search.
So in the end, it doesn't matter if you have sorted the values or not, because MySQL will sort them even if they are not. Nevertheless this wasn't your question. Now let's get back to your question.
First of all, deadlocks are absolutely possible in MySQL when your are using InnoDb and they happen all the time (at least to me). The strategy you've chosen to prevent deadlocks is a valid one (acquiring locks according to some order). But unfortunately I don't think it's going to work in MySQL. You see, even though in your query it is clearly stated which records you want to be locked, but the truth is that they are not the only records that will be locked:
A locking read, an UPDATE, or a DELETE generally set record locks on
every index record that is scanned in the processing of the SQL
statement. It does not matter whether there are WHERE conditions in
the statement that would exclude the row. InnoDB does not remember the
exact WHERE condition, but only knows which index ranges were scanned.
The locks are normally next-key locks that also block inserts into the
“gap” immediately before the record. However, gap locking can be
disabled explicitly, which causes next-key locking not to be used.
So it's hard to say which records are actually locked. Now consider MySQL is searching the index for the first value in your list. As I've just said few more records might be locked along the way while MySQL is scanning the index. And since scanning indices does not happen in order (or at least that's what I believe), records would get locked regardless of their order. Which means that deadlocks are not prevented.
The last part is my own understanding of the situation and I've actually never read that anywhere before. But in theory it sounds right. Yet I really would like someone to prove me wrong (just so I can trust MySQL even more).
Ok so this question is not really that clear as to what you want... so this may not be an answer to your question.. but I made some test stuff to help you visualize the data and how IN() works.. so I hope thats at least helpful.
SETUP:
CREATE TABLE table1
(`id` int, `username` varchar(10), `t1` varchar(55));
INSERT INTO table1
(`id`, `username`, `t1`)
VALUES
(4, 'John', 'Hi1'),
(3, 'Ram ', 'Hi2'),
(2, 'Jack', 'Hi3'),
(1, 'Jill', 'Hi4');
CREATE TABLE table2
(`id` int, `username` varchar(10), `t1` varchar(55));
INSERT INTO table2
(`id`, `username`, `t1`)
VALUES
(1, 'Joe', 'Hey1'),
(2, 'Fes', 'Hey2'),
(3, 'Ned', 'Hey3'),
(4, 'Abe', 'Hey4');
I made table1 have a backwards ID.. aka 4, 3, 2, 1 and then table2 has the regular incremented id. 1, 2, 3, 4...
1. SELECT * FROM table1
RESULT-OF-1
2. SELECT * FROM table2
RESULT-OF-2
3. SELECT * FROM table1 WHERE id = 1 OR id = 2 OR id = 3 OR id = 4 same result as 1
4. SELECT * FROM table1 WHERE id IN(1, 2, 3, 4).. same result as 1.
5. SELECT * FROM table1 WHERE id IN(1, 4, 3, 2).. same result as 1.
IN() compares the id from each row in the table to what is specified inside the IN() statement.. if it matches it will return the row.. so it returns the data in the "natural table ordering" .. more info HERE
there is a way to do the update you posted with an IN() statement.. without writing out each update.
UPDATE table1 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UPDATE table2 SET t2= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
all you do here is combine the 'hello' string with the ID and set your column to it since that is what you posted. I hope that helps understand how the data gets pulled out.
output for the two updates : table1.... table2
for locking tables to update you should probably lock them to WRITE and UNLOCK to prevent a deeplock. see post
LOCK TABLES table1 WRITE, table2 WRITE;
UPDATE table1 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UPDATE table2 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UNLOCK TABLES;
Rows are locked in the order they are read, so no order is guaranteed. Even if you add an ORDER BY clause, the rows will be locked as they are read, not as they are ordered. Here is another good question with some great answers

When using MySQL's FOR UPDATE locking, what is exactly locked?

This is not a full/correct MySQL query only pseudo-code:
Select *
from Notifications as n
where n.date > (CurrentDate-10 days)
limit by 1
FOR UPDATE
http://dev.mysql.com/doc/refman/5.0/en/select.html states:
If you use FOR UPDATE with a storage engine that uses page or row locks, rows examined by the query are write-locked until the end of the current transaction
Is here only the one record returned locked by MySQL or all records it has to scan to find the single record?
Why don't we just try it?
Set up the database
CREATE DATABASE so1;
USE so1;
CREATE TABLE notification (`id` BIGINT(20), `date` DATE, `text` TEXT) ENGINE=InnoDB;
INSERT INTO notification(id, `date`, `text`) values (1, '2011-05-01', 'Notification 1');
INSERT INTO notification(id, `date`, `text`) values (2, '2011-05-02', 'Notification 2');
INSERT INTO notification(id, `date`, `text`) values (3, '2011-05-03', 'Notification 3');
INSERT INTO notification(id, `date`, `text`) values (4, '2011-05-04', 'Notification 4');
INSERT INTO notification(id, `date`, `text`) values (5, '2011-05-05', 'Notification 5');
Now, start two database connections
Connection 1
BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;
Connection 2
BEGIN;
If MySQL locks all rows, the following statement would block. If it only locks the rows it returns, it shouldn't block.
SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;
And indeed it does block.
Interestingly, we also cannot add records that would be read, i.e.
INSERT INTO notification(id, `date`, `text`) values (6, '2011-05-06', 'Notification 6');
blocks as well!
I can't be sure at this point whether MySQL just goes ahead and locks the entire table when a certain percentage of rows are locked, or where it's actually really intelligent in making sure the result of the SELECT ... FOR UPDATE query can never be changed by another transaction (with an INSERT, UPDATE, or DELETE) while the lock is being held.
The thread is pretty old, just to share my two cents regarding the tests above performed by #Frans
Connection 1
BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;
Connection 2
BEGIN;
SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;
The concurrent transaction 2 will be blocked for sure, but the reason is NOT that the transaction 1 is holding the lock on the whole table. The following explains what has happened behind the scene:
First of all, the default isolation level of the InnoDB storage engine is Repeatable Read. In this case,
1- When the column used in where condition is not indexed (as the case above):
The engine is obliged to perform a full table scan to filter out the records not matching the criteria. EVERY ROW that have been scanned are locked in the first place. MySQL may release the locks on those records not matching the where clause later on. It is an optimization for the performance, however, such behavior violates the 2PL constraint.
When transaction 2 starts, as explained, it needs to acquire the X lock for each row retrieved although there exists only a single record (id = 2) matching the where clause. Eventually the transaction 2 will be waiting for the X lock of the first row (id = 1) until the transaction 1 commits or rollbacks.
2- When the column used in where condition is a primary index
Only the index entry satisfying the criteria is locked. That's why in the comments someone says that some tests are not blocked.
3 - When the column used in where condition is an index but not unique
This case is more complicated. 1) The index entry is locked. 2) One X lock is attached to the corresponding primary index. 3) Two gap locks are attached to the non-existing entries right before and after the record matching the search criteria.
I know this question is pretty old, but I've wanted to share the results of some relevant testing I've done with indexed columns which has yielded some pretty strange results.
Table structure:
CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`notid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
12 rows inserted with INSERT INTO t1 (notid) VALUES (1), (2),..., (12). On connection 1:
BEGIN;
SELECT * FROM t1 WHERE id=5 FOR UPDATE;
On connection 2, the following statements are blocked:
SELECT * FROM t1 WHERE id!=5 FOR UPDATE;
SELECT * FROM t1 WHERE id<5 FOR UPDATE;
SELECT * FROM t1 WHERE notid!=5 FOR UPDATE;
SELECT * FROM t1 WHERE notid<5 FOR UPDATE;
SELECT * FROM t1 WHERE id<=4 FOR UPDATE;
The strangest part is that SELECT * FROM t1 WHERE id>5 FOR UPDATE; is not blocked, nor are any of
...
SELECT * FROM t1 WHERE id=3 FOR UPDATE;
SELECT * FROM t1 WHERE id=4 FOR UPDATE;
SELECT * FROM t1 WHERE id=6 FOR UPDATE;
SELECT * FROM t1 WHERE id=7 FOR UPDATE;
...
I'd also like to point out that it seems the entire table is locked when the WHERE condition in the query from connection 1 matches a non-indexed row. For example, when connection 1 executes SELECT * FROM t1 WHERE notid=5 FOR UPDATE, all select queries with FOR UPDATE and UPDATE queries from connection 2 are blocked.
-EDIT-
This is a rather specific situation, but it was the only I could find that exhibits this behaviour:
Connection 1:
BEGIN;
SELECT *, #x:=#x+id AS counter FROM t1 CROSS JOIN (SELECT #x:=0) b HAVING counter>5 LIMIT 1 FOR UPDATE;
+----+-------+-------+---------+
| id | notid | #x:=0 | counter |
+----+-------+-------+---------+
| 3 | 3 | 0 | 9 |
+----+-------+-------+---------+
1 row in set (0.00 sec)
From connection 2:
SELECT * FROM t1 WHERE id=2 FOR UPDATE; is blocked;
SELECT * FROM t1 WHERE id=4 FOR UPDATE; is not blocked.
Following links from the documentation page you posted gives more information about locking. In this page
A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.
This seems pretty clear that it is all rows that it has to scan.
From mysql official doc:
A locking read, an UPDATE, or a DELETE generally set record locks on every index record that is scanned in the processing of the SQL statement. It does not matter whether there are WHERE conditions in the statement that would exclude the row.
For the case discussed in Frans' answer, all rows are locked because there's a table scan during sql processing:
If you have no indexes suitable for your statement and MySQL must scan the entire table to process the statement, every row of the table becomes locked, which in turn blocks all inserts by other users to the table. It is important to create good indexes so that your queries do not unnecessarily scan many rows.
Check the latest doc here: https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html
As others have mentioned, SELECT... FOR UPDATE locks all rows encountered in the default isolation level. Try setting the isolation for the session which runs this query to READ COMMITTED, for example precede the query with: set session transaction isolation level read committed;
It locks all the rows selected by query.

How many rows will be locked by SELECT ... ORDER BY xxx LIMIT 1 FOR UPDATE?

I have a query with the following structure:
SELECT ..... WHERE status = 'QUEUED' ORDER BY position ASC LIMIT 1 FOR UPDATE;
It's a single-table SELECT statement on InnoDB table. Field position (INT NOT NULL) has an index on it. status is ENUM and is also indexed.
SELECT ... FOR UPDATE manual page says, that it locks all rows it reads. Do I understand correctly, that in this case only one row will be locked? Or rather it will lock the whole table?
Is that possible to determine which rows will be locked with EXPLAIN query? If yes - how? Explain for a query on the empty table shows the following:
1;'SIMPLE';'job';'index';<null>;'index_position';[34,...];<null>;1;'Using where'
This is a great question. InnoDB is a row level locking engine, but it has to set additional locks to ensure safety with the binary log (used for replication; point in time recovery). To start explaining it, consider the following (naive) example:
session1> START TRANSACTION;
session1> DELETE FROM users WHERE is_deleted = 1; # 1 row matches (user_id 10), deleted.
session2> START TRANSACTION;
session2> UPDATE users SET is_deleted = 1 WHERE user_id = 5; # 1 row matches.
session2> COMMIT;
session1> COMMIT;
Because statements are only written to the binary log once committed, on the slave session#2 would apply first, and would produce a different result, leading to data corruption.
So what InnoDB does, is sets additional locks. If is_deleted is indexed, then before session1 commits nobody else will be able to modify or insert into the range of records where is_deleted=1. If there are no indexes on is_deleted, then InnoDB needs to lock every row in the entire table to make sure the replay is in the same order. You can think of this as locking the gap, which is different concept to grasp from row-level locking directly.
In your case with that ORDER BY position ASC, InnoDB needs to make sure that no new rows could be modified between the lowest key value and a "special" lowest possible value. If you did something like ORDER BY position DESC.. well, then nobody could insert into this range.
So here comes the solution:
Statement based binary logging sucks. I really look forward to a future where we all switch to row based binary logging (available from MySQL 5.1, but not on by default).
With Row-based replication, if you change the isolation level to read-committed, then only the one row that matches needs to be locked.
If you want to be a masochist, you can also turn on innodb_locks_unsafe_for_binlog with statement-based replication.
Update 22 April: To copy + paste my improved version of your testcase (it was not searching 'in the gap'):
session1> CREATE TABLE test (id int not null primary key auto_increment, data1 int, data2 int, INDEX(data1)) engine=innodb;
Query OK, 0 rows affected (0.00 sec)
session1> INSERT INTO test VALUES (NULL, 1, 2), (NULL, 2, 1), (5, 2, 2), (6, 3, 3), (3, 3, 4), (4, 4, 3);
Query OK, 6 rows affected (0.00 sec)
Records: 6 Duplicates: 0 Warnings: 0
session1> start transaction;
Query OK, 0 rows affected (0.00 sec)
session1> SELECT id FROM test ORDER BY data1 LIMIT 1 FOR UPDATE;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
session2> INSERT INTO test values (NULL, 0, 99); # blocks - 0 is in the gap between the lowest value found (1) and the "special" lowest value.
# At the same time, from information_schema:
localhost information_schema> select * from innodb_locks\G
*************************** 1. row ***************************
lock_id: 151A1C:1735:4:2
lock_trx_id: 151A1C
lock_mode: X,GAP
lock_type: RECORD
lock_table: `so5694658`.`test`
lock_index: `data1`
lock_space: 1735
lock_page: 4
lock_rec: 2
lock_data: 1, 1
*************************** 2. row ***************************
lock_id: 151A1A:1735:4:2
lock_trx_id: 151A1A
lock_mode: X
lock_type: RECORD
lock_table: `so5694658`.`test`
lock_index: `data1`
lock_space: 1735
lock_page: 4
lock_rec: 2
lock_data: 1, 1
2 rows in set (0.00 sec)
# Another example:
select * from test where id < 1 for update; # blocks
I've made tests. Created the following table:
id data1 data2
1 1 2
2 2 1
5 2 2
6 3 3
3 3 4
4 4 3
Then I created first connection with transaction:
SELECT id FROM test ORDER BY data1 LIMIT 1 FOR UPDATE;
result was the row with id=1;
Then I created second transaction from another connection without commiting first:
SELECT id FROM test WHERE data1=2 FOR UPDATE;
It didn't block. And it blocked only when I tried to select the very row selected by the first transaction. I tried the following with changing ORDER BY to DESC one, it works also.
Conclusion: MySQL blocks only the rows it actually selected when using ORDER BY and LIMIT clauses. See #Morgan answer for gap locking explanation.
My MySQL version is 5.0.45
There is a bug in some versions of MySQL: #67745 Too much row locks when using SELECT for UPDATE, LIMIT and ORDER BY.
Version: 5.5.28, 5.5.30, 5.7.1
Same bug on my local mysql 5.5.25 win64.
Unlike other databases, in MySQL the query will lock the index positions. This effectively means that all rows that currently have status equal to 'QUEUED' or would like it to have changed to 'QUEUED' from another transaction are locked. The only solution I've found to this is selecting the rows without FOR UPDATE, then selecting them with a ID-based filter and re-checking the condition once they are locked. Not nice, but it does the job.