8

We have a table in MySQL using InnoDB, and we are using a transaction isolation level of read uncommitted. Why does setting @x as shown acquire a lock?

mysql> set @x = (select userID from users limit 1);
Query OK, 0 rows affected (0.02 sec)

mysql>

Trying to update this table from another prompt results in a timeout error:

mysql> update users set userID = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
Yoseph
  • 730
  • 1
  • 7
  • 8
  • What happens if @x is subsequently set to NULL? (And I *assume* it's in a transaction?) –  Oct 31 '12 at 05:41
  • setting @x to null does not make a difference, and yes it is in a transaction (using transaction isolation level of read uncommitted) – Yoseph Nov 01 '12 at 12:34
  • 2
    MySQL has confirmed this as a bug: http://bugs.mysql.com/bug.php?id=67452 – Yoseph Feb 04 '13 at 09:22

2 Answers2

2

For what it's worth, this locking is not limited to READ-UNCOMMITTED:

mysql1> show variables like '%isolation%';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
mysql1> BEGIN;
mysql1> SET @x := (SELECT x FROM foo LIMIT 1);

mysql2> UPDATE foo SET x = x+1;
[gets a lock wait]

mysql3> SHOW ENGINE INNODB STATUS;
...
---TRANSACTION 228746, ACTIVE 22 sec
2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 58, OS thread handle 0x7fc262a1c700, query id 8163
  192.168.56.1 root cleaning up
TABLE LOCK table `test`.`foo` trx id 228746 lock mode IS
RECORD LOCKS space id 801 page no 3 n bits 80 index `PRIMARY` 
  of table `test`.`foo` trx id 228746 lock mode S
...

As discussed in the bug you logged, Bug #67452 Setting a variable from a select acquires a lock when using read uncommitted, this behavior is probably by design. It seems to fall into the same category as SELECT statements whose results are used to modify data, like these cases described:

http://dev.mysql.com/doc/refman/5.6/en/innodb-locks-set.html

When a SELECT is used in the constructs REPLACE INTO t SELECT ... FROM s WHERE ... or UPDATE t ... WHERE col IN (SELECT ... FROM s ...), InnoDB sets shared next-key locks on rows from table s.

The reason for next-key locks is to make the SELECT results more stable. That is, we don't want the rows matched by the SELECT to change while they are being used for an UPDATE or other data-modifying statement.

Even when the tx_isolation is REPEATABLE-READ, this is important because InnoDB doesn't support REPEATABLE-READ for SELECT statements when they're executed as part of any type of UPDATE.


Re your comment:

Regardless of the documentation, here's what happens:

When you do plain SELECT statement, InnoDB does not lock anything, in any transaction isolation except SERIALIZABLE.

If you do a SELECT ... LOCK IN SHARE MODE or SELECT ... FOR UPDATE, it locks of course.

But when you do SELECT as part of a data-modifying statement like INSERT INTO...SELECT or in a subquery of an UPDATE or as you found in a SET @variable := (SELECT...), it uses a shared lock to make sure the data doesn't change while the update is in progress.

Documentation can be incomplete. Better to test.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • It is both stated and obvious that for Repeatable Read MySQL will acquire locks for select statements (I have no issue with that as that is how one would expect a Repeatable Read to be done). If you follow the link that you have quoted you will see it links to http://dev.mysql.com/doc/refman/5.6/en/innodb-locks-set.html which states 'READ UNCOMMITTED: SELECT statements are performed in a nonlocking fashion.', hence my issue with Read Uncommitted does not fall into that category. The even when logic doesn't work when talking about Repeatable Read as by definition that does more locking. – Yoseph May 03 '14 at 12:03
0

Your first statement executes a SELECT on the table, therefore the transaction acquires a read lock on one row.

The second transaction tries to acquire a write lock on the same table (on all rows, since there is no WHERE clause), but cannot.

You need to issue a COMMIT (or ROLLBACK) command after the SET @x = (...), so that it releases the read lock.

The above is wrong. I keep this post just because the below comments might be of interest.

RandomSeed
  • 29,301
  • 6
  • 52
  • 87
  • But the select from the first statement shouldn't prevent an UPDATE on that table. Is MySQL really that bad? –  Oct 31 '12 at 13:51
  • @a_horse_with_no_name Well, by definition, a READ lock prevents concurrent updates, I do not see this as specific to MySQL. I must be missing something in the question (on the other hand, several transactions may acquire simultaneous READ locks, but that's another story). Of course, I assume the `SET @x=(SELECT)` statement was run inside a transaction (the problem should not occur otherwise anyways). – RandomSeed Oct 31 '12 at 14:07
  • Oracle, Postgres, Firebird, DB2 (any DBMS using MVCC) do not block concurrent reads and writes. The sequence from the question would work perfectly fine there (I always run with autocommit off) –  Oct 31 '12 at 14:08
  • @a_horse_with_no_name My bad. The issue does not appear with MySQL either if one just issue a `SELECT (...)` statement. It only happens with a `SET @x = (SELECT... )` statement. Thank you for correcting me. I misread the question. – RandomSeed Oct 31 '12 at 14:33
  • 2
    I did some testing on this and it seems to be a general problem with sub-selects in DML statements. `update foo set some_value = 'bar' where id in (select id from users)` will also block writes to the `users`(!) table, even though that hasn't been updated at all. –  Oct 31 '12 at 15:01
  • Could it be related to this bug: http://bugs.mysql.com/bug.php?id=46759 ? I have the exact same behaviour here on version 5.5.24-0ubuntu0.12.04.1-log – RandomSeed Oct 31 '12 at 15:35
  • My question concerns doing this when using transaction isolation read uncommitted. With repeatable read I could possibly understand that a select could acquire a lock, but with read uncommitted I can not understand why this would be the case. My original findings were with 5.6 I think, but I have just tested these also on 5.5.24-0ubuntu0.12.04.1 and got the same results. The updates with nested selects, I can only reproduce with repeatable read, and hence bug 46759 does not appear to answer my question. – Yoseph Nov 01 '12 at 13:30
  • Also note on 5.5.24-0ubuntu0.12.04.1 I had to use `set session transaction isolation level read uncommitted;` as there appears to be a bug with using `global` (with global the transaction isolation was not changed) – Yoseph Nov 01 '12 at 13:36