Bug #25180 Sub-select fails when table is locked
Submitted: 19 Dec 2006 15:45 Modified: 10 Jan 2007 18:40
Reporter: Karl Lloyd Email Updates:
Status: Duplicate Impact on me:
None 
Category:MySQL Server: Locking Severity:S3 (Non-critical)
Version:5.0.34-BK, 5.0.22 OS:Linux (Linux, ubuntu 6.06.2)
Assigned to: CPU Architecture:Any

[19 Dec 2006 15:45] Karl Lloyd
Description:
Don't know whether this is a bug as such, or intended behaviour, but to me it seems illogical:

After you have done a LOCK TABLE `foo` WRITE, if your subsequent SELECT contains a sub-SELECT, such as "SELECT * FROM `foo` WHERE `bar` NOT IN (SELECT DISTINCT `bar` FROM `foo` WHERE `pending`=1) AND `updated`=0 LIMIT 1;" it fails with an "ERROR 1100 (HY000): Table 'foo' was not locked with LOCK TABLES".

At a quick guess, I presume the "SELECT DISTINCT `bar` FROM `foo` WHERE `pending`=1" is performed by a child thread which is locked out of `foo` even though, in my opinion at least, it shouldn't be. Perhaps I'm wrong and something else is going on, but I still think it's not doing what it should.

How to repeat:
mysql> CREATE TABLE `foo` (
  `id` int(11) NOT NULL auto_increment,
  `at` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  `bar` varchar(15) NOT NULL,
  `pending` tinyint(1) NOT NULL default '0',
  `updated` tinyint(1) NOT NULL default '0',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO `foo` (`bar`, `pending`, `updated`) VALUES
('abcde', 1, 1),
('abcde', 1, 0),
('fghij', 1, 0),
('fghij', 0, 1),
('fghij', 0, 0),
('klmno', 0, 0),
('pqrst', 0, 0),
('pqrst', 0, 0),
('uvwxy', 0, 0);
Query OK, 9 rows affected (0.01 sec) Records: 9  Duplicates: 0  Warnings: 0

mysql> LOCK TABLE `foo` WRITE;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM `foo` WHERE `bar` NOT IN (SELECT DISTINCT `bar` FROM `foo` WHERE `pending`=1) AND `updated`=0 LIMIT 1;
ERROR 1100 (HY000): Table 'foo' was not locked with LOCK TABLES

mysql> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM `foo` WHERE `bar` NOT IN (SELECT DISTINCT `bar` FROM `foo` WHERE `pending`=1) AND `updated`=0;
+----+---------------------+-------+---------+---------+
| id | at                  | bar   | pending | updated |
+----+---------------------+-------+---------+---------+
|  6 | 2006-12-19 15:38:05 | klmno |       0 |       0 |
|  7 | 2006-12-19 15:38:05 | pqrst |       0 |       0 |
|  8 | 2006-12-19 15:38:05 | pqrst |       0 |       0 |
|  9 | 2006-12-19 15:38:05 | uvwxy |       0 |       0 |
+----+---------------------+-------+---------+---------+
4 rows in set (0.00 sec)

mysql> DROP TABLE `foo`;
Query OK, 0 rows affected (0.02 sec)
[19 Dec 2006 18:52] Valeriy Kravchuk
Thank you for a bug report. Verified just as described with 5.0.34-BK on Linux:

mysql> CREATE TABLE `foo` (
    ->   `id` int(11) NOT NULL auto_increment,
    ->   `at` timestamp NOT NULL default CURRENT_TIMESTAMP on update
    -> CURRENT_TIMESTAMP,
    ->   `bar` varchar(15) NOT NULL,
    ->   `pending` tinyint(1) NOT NULL default '0',
    ->   `updated` tinyint(1) NOT NULL default '0',
    ->   PRIMARY KEY  (`id`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO `foo` (`bar`, `pending`, `updated`) VALUES
    -> ('abcde', 1, 1),
    -> ('abcde', 1, 0),
    -> ('fghij', 1, 0),
    -> ('fghij', 0, 1),
    -> ('fghij', 0, 0),
    -> ('klmno', 0, 0),
    -> ('pqrst', 0, 0),
    -> ('pqrst', 0, 0),
    -> ('uvwxy', 0, 0);
Query OK, 9 rows affected (0.01 sec)
Records: 9  Duplicates: 0  Warnings: 0

mysql> LOCK TABLE `foo` WRITE;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM `foo` WHERE `bar` NOT IN (SELECT DISTINCT `bar` FROM `foo`
    -> WHERE `pending`=1) AND `updated`=0 LIMIT 1;
ERROR 1100 (HY000): Table 'foo' was not locked with LOCK TABLES
mysql>  UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM `foo` WHERE `bar` NOT IN (SELECT DISTINCT `bar` FROM `foo`
 WHERE `pending`=1) AND `updated`=0 LIMIT 1;
+----+---------------------+-------+---------+---------+
| id | at                  | bar   | pending | updated |
+----+---------------------+-------+---------+---------+
|  6 | 2006-12-19 17:26:12 | klmno |       0 |       0 |
+----+---------------------+-------+---------+---------+
1 row in set (0.01 sec)

mysql> select version();
+--------------+
| version()    |
+--------------+
| 5.0.34-debug |
+--------------+
1 row in set (0.00 sec)

Even if this is intended behaviour, error message looks misleading. So, it is a bug in any case.
[9 Jan 2007 18:10] Steven Adams
This affects more than just subqueries; it affects UNIONs as well. Replace the original SELECT with this one:

(SELECT * FROM `foo` WHERE `bar` IN ('abcde','fghij')) UNION (SELECT * FROM `foo` WHERE `bar` IN ('pqrst'));

and the same results occur. Note that I am using version 4.1.21. When testing this same case in 4.0.27, it works.

mysql> CREATE TABLE `foo` (
    ->   `id` int(11) NOT NULL auto_increment,
    ->   `at` timestamp NOT NULL default CURRENT_TIMESTAMP on update
    ->     CURRENT_TIMESTAMP,
    ->   `bar` varchar(15) NOT NULL,
    ->   `pending` tinyint(1) NOT NULL default '0',
    ->   `updated` tinyint(1) NOT NULL default '0',
    ->   PRIMARY KEY  (`id`)
    -> ) ENGINE=MyISAM;

Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO `foo` (`bar`, `pending`, `updated`) VALUES
    ->   ('abcde', 1, 1),
    ->   ('abcde', 1, 0),
    ->   ('fghij', 1, 0),
    ->   ('fghij', 0, 1),
    ->   ('fghij', 0, 0),
    ->   ('klmno', 0, 0),
    ->   ('pqrst', 0, 0),
    ->   ('pqrst', 0, 0),
    ->   ('uvwxy', 0, 0);
Query OK, 9 rows affected (0.01 sec)
Records: 9  Duplicates: 0  Warnings: 0

mysql> LOCK TABLE `foo` WRITE;
Query OK, 0 rows affected (0.00 sec)

mysql> (SELECT * FROM `foo` WHERE `bar` IN ('abcde','fghij')) UNION (SELECT * FROM `foo` WHERE `bar` IN ('pqrst'));
ERROR 1100 (HY000): Table 'foo' was not locked with LOCK TABLES

mysql> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

mysql> (SELECT * FROM `foo` WHERE `bar` IN ('abcde','fghij')) UNION (SELECT * FROM `foo` WHERE `bar` IN ('pqrst'));
+----+---------------------+-------+---------+---------+
| id | at                  | bar   | pending | updated |
+----+---------------------+-------+---------+---------+
|  1 | 2007-01-09 12:02:15 | abcde |       1 |       1 |
|  2 | 2007-01-09 12:02:15 | abcde |       1 |       0 |
|  3 | 2007-01-09 12:02:15 | fghij |       1 |       0 |
|  4 | 2007-01-09 12:02:15 | fghij |       0 |       1 |
|  5 | 2007-01-09 12:02:15 | fghij |       0 |       0 |
|  7 | 2007-01-09 12:02:15 | pqrst |       0 |       0 |
|  8 | 2007-01-09 12:02:15 | pqrst |       0 |       0 |
+----+---------------------+-------+---------+---------+
7 rows in set (0.00 sec)

mysql> select version();
+------------+
| version()  |
+------------+
| 4.1.21-log |
+------------+
1 row in set (0.00 sec)
[10 Jan 2007 12:29] Karl Lloyd
Just to follow up on this further: interestingly, following a "LOCK TABLE `foo` WRITE;" the following query is successful:

mysql> SELECT `at` FROM (SELECT * FROM `foo` WHERE `pending`=1) AS `baz` WHERE `updated` = 0;
+---------------------+
| at                  |
+---------------------+
| 2007-01-10 11:43:04 |
| 2007-01-10 11:43:04 |
+---------------------+
2 rows in set (0.01 sec)

but the following are not:

mysql> SELECT `at`, (SELECT COUNT(*) FROM `foo` WHERE `pending`=1) AS `count` FROM `foo` WHERE `updated` = 1;
ERROR 1100 (HY000): Table 'foo' was not locked with LOCK TABLES

mysql> INSERT INTO `foo` (`at`, `bar`, `pending`, `updated`) (SELECT `at`, 'ABCDE', `pending`, `updated` FROM `foo` WHERE `pending` = 1 AND `updated` = 1);
ERROR 1100 (HY000): Table 'foo' was not locked with LOCK TABLES

mysql> SELECT * FROM `foo` ORDER BY (SELECT COUNT(`bar`) FROM `foo`);
ERROR 1100 (HY000): Table 'foo' was not locked with LOCK TABLES
(although I'm not sure of the extent of support for subselects within ORDER BY clauses)

and, by adding another table:
mysql> CREATE TABLE `bars` (
    ->   `id` int(11) NOT NULL auto_increment,
    ->   `bar` varchar(15) NOT NULL,
    ->   `pending` tinyint(1) NOT NULL default '0',
    ->   `updated` tinyint(1) NOT NULL default '0',
    ->   PRIMARY KEY  (`id`)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO `foo` (`bar`, `pending`, `updated`) VALUES
    ->   ('abcde', 1, 1),
    ->   ('abcde', 1, 0),
    ->   ('fghij', 1, 0),
    ->   ('fghij', 0, 1),
    ->   ('fghij', 0, 0),
    ->   ('klmno', 0, 0),
    ->   ('pqrst', 0, 0),
    ->   ('pqrst', 0, 0),
    ->   ('uvwxy', 0, 0);
Query OK, 9 rows affected (0.02 sec)
Records: 9  Duplicates: 0  Warnings: 0

mysql> UPDATE `foo` SET `bar` = (SELECT REVERSE(`bar`) FROM `bars` WHERE `id` = 1) WHERE `pending` = 1 AND `updated` = 1;
ERROR 1100 (HY000): Table 'bars' was not locked with LOCK TABLES

mysql> UNLOCK TABLES; Query OK, 0 rows affected (0.01 sec)

mysql> DROP TABLE `bars`;
Query OK, 0 rows affected (0.00 sec)

mysql> DROP TABLE `foo`;
Query OK, 0 rows affected (0.00 sec)

Perhaps this is helpful info dev-wise.
[10 Jan 2007 12:33] Karl Lloyd
Should have been "INSERT INTO `bars`" in my previous submission, of course.
[10 Jan 2007 14:40] Dmitry Lenev
Hi, Karl!

Thank you for your interest in MySQL!

This report is duplicate of already known "Not a Bug" bug #5651.

Altough I agree that this restriction is not very natural and
error message can be improved. Feel free to add appropriate
feature request!
[10 Jan 2007 16:33] Karl Lloyd
Yes, I concede that in the last example (UPDATE with subselect) I should have locked both `foo` and `bars` and does indeed work when this is done. However, according to the documentation quoted in #5651 (http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html) the following should work:

mysql> LOCK TABLES `foo` WRITE, `foo` `oof` WRITE;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM `foo` WHERE `bar` NOT IN (SELECT DISTINCT `bar` FROM `oof` WHERE `pending`=1) AND `updated`=0 LIMIT 1;
ERROR 1100 (HY000): Table 'oof' was not locked with LOCK TABLES

but doesn't. As this is different from my initial submission, and I think this IS a bug, perhaps I should submit this as a new report?
[10 Jan 2007 18:34] Dmitry Lenev
Hi, Karl!

Still this is not a bug. This query is not supposed to work as it refers to the aliased table incorrectly. Please re-read documentation, particularly pay attention to the example below:

<quote>
Conversely, if you lock a table using an alias, you must refer to it in your queries using that alias:

mysql> LOCK TABLE t AS myalias READ;
mysql> SELECT * FROM t;
ERROR 1100: Table 't' was not locked with LOCK TABLES
mysql> SELECT * FROM t AS myalias;
</quote>
[10 Jan 2007 18:40] Karl Lloyd
Apologies, my syntax was wrong. Many thanks for your help.