我发现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想要尝试这个吗?
答案 0 :(得分:2)
从依赖于设置索引列的值更高或更低,看起来锁实际上被放置在索引条目上。数据库引擎扫描索引,并在第一个锁定的条目处停止,等待它被释放。
提交第一个事务时,索引被解锁,等待事务继续扫描索引。因为价值降低了,所以它现在在索引的早期。因此,恢复的扫描没有看到它,因为它已经通过了那一点。
要确认这一点,请尝试以下测试:
SELECT ... FOR UPDATE
如果我的猜测是正确的,事务2应该只返回4行。
这对我来说似乎是一个错误,因为我认为你不应该得到这样的部分结果。不幸的是,很难在bugs.mysql.com上搜索这个,因为搜索时会忽略“for”这个词,因为它太短或太常见。甚至引用“for update”似乎也没有找到只包含这个短语的错误。