即使存在行,MySQL select for update也会返回空集

时间:2014-11-03 21:56:25

标签: mysql locking

我发现MySQL的一个奇怪的问题"选择更新"。我使用的是5.1.45版。我有两张桌子:

    mysql> show create table tag;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                                      |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| tag   | CREATE TABLE `tag` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `message` varchar(255) NOT NULL,
  `created_at` bigint(20) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> show create table live_tag;
+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                           |
+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| live_tag | CREATE TABLE `live_tag` (
  `tag_id` int(10) unsigned NOT NULL,
  KEY `live_tag_tag_fk` (`tag_id`),
  CONSTRAINT `live_tag_tag_fk` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

用户保存的第一个商店版本("标签")以及提交消息。第二个表包含当前" live"的版本的id。 live_tag.tag_id引用标记(id)上有一个外键。 live_tag只包含一行。提交新版本时,将更新此行。在更新live_tag行之前,我执行以下语句:

mysql> select tag_id from live_tag for update;

但是,当我在两个终端中运行此语句并更新其中一个中的tag_id时,有时MySQL会返回"空集"在第二个终端而不是新值:

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

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

-- TERMINAL ONE
mysql> select tag_id from live_tag for update;
+--------+
| tag_id |
+--------+
|      2 |
+--------+
1 row in set (0.00 sec)

-- TERMINAL TWO
mysql> select tag_id from live_tag for update;
-- hangs (waiting for lock)

-- TERMINAL ONE
mysql> update live_tag set tag_id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

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

-- TERMINAL TWO returns the following for previous "select tag_id from live_tag for update"
Empty set (8.54 sec) -- Why empty set?

我没有删除任何行,我只是更新了live_tag中的一行,为什么MySQL没有看到更新?

更奇怪的是,我注意到如果我将live_tag设置为比以前更高的值,第二个终端正确返回新值:

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

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

-- TERMINAL ONE
mysql> select tag_id from live_tag for update;
+--------+
| tag_id |
+--------+
|      1 |
+--------+
1 row in set (0.00 sec)

-- TERMINAL TWO
mysql> select tag_id from live_tag for update;
-- hangs (waiting for lock)

-- TERMINAL ONE
mysql> update live_tag set tag_id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

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

-- TERMINAL TWO returns the following for previous "select tag_id from live_tag for update"
+--------+
| tag_id |
+--------+
|      2 |
+--------+
-- this is correct

仅当我将tag_id设置为比以前更低的值时才会出现问题。

这是由于tag_id上的外键约束吗?或者因为我选择了表格中的所有行(没有' where'子句)?

我已经尝试过的事情:

  • 删除live_tag.tag_id上的键后,它可以正常工作。

  • 我在live_tag中添加了一个id列,并限制了我选择更新'与'其中id = 1'。这也可以正常工作。

  • 我用三个终端尝试了这个。提交1,2后立即返回空集。几秒钟后,3也返回空集(即使我还没有提交2)。

我可以将id列添加到表中,但仍然对这种奇怪的行为感到好奇吗?我在这里尝试使用谷歌搜索和搜索,但没有找到答案。

更新

Barmar的理论似乎是正确的,因为我尝试了他的建议测试,并在响应中只获得了一行:

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

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

-- TERMINAL ONE
mysql> select tag_id from live_tag for update;
+--------+
| tag_id |
+--------+
|      2 |
|      3 |
+--------+
2 rows in set (0.00 sec)

-- TERMINAL TWO
mysql> select tag_id from live_tag for update;
-- hangs

-- TERMINAL ONE
mysql> update live_tag set tag_id=1 where tag_id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update live_tag set tag_id=4 where tag_id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from live_tag;
+--------+
| tag_id |
+--------+
|      1 |
|      4 |
+--------+
2 rows in set (0.00 sec)

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

-- TERMINAL TWO returns
+--------+
| tag_id |
+--------+
|      4 |
+--------+
1 row in set (34.02 sec)

任何人都有更新版本的MySQL想要尝试这个吗?

1 个答案:

答案 0 :(得分:2)

从依赖于设置索引列的值更高或更低,看起来锁实际上被放置在索引条目上。数据库引擎扫描索引,并在第一个锁定的条目处停止,等待它被释放。

提交第一个事务时,索引被解锁,等待事务继续扫描索引。因为价值降低了,所以它现在在索引的早期。因此,恢复的扫描没有看到它,因为它已经通过了那一点。

要确认这一点,请尝试以下测试:

  1. 创建两行,值为2和3。
  2. 在两笔交易中,执行SELECT ... FOR UPDATE
  3. 在事务1中,将2更改为1,将3更改为4。
  4. 提交交易1。
  5. 如果我的猜测是正确的,事务2应该只返回4行。

    这对我来说似乎是一个错误,因为我认为你不应该得到这样的部分结果。不幸的是,很难在bugs.mysql.com上搜索这个,因为搜索时会忽略“for”这个词,因为它太短或太常见。甚至引用“for update”似乎也没有找到只包含这个短语的错误。