【问题标题】:In MySQL Why does setting a variable from a select acquire a lock when using read uncommitted?在 MySQL 中,为什么在使用未提交读取时从选择中设置变量会获取锁?
【发布时间】:2012-10-31 05:37:02
【问题描述】:

我们在 MySQL 中有一个使用 InnoDB 的表,并且我们使用未提交读的事务隔离级别。为什么如图所示设置@x 会获得锁?

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

mysql>

尝试从另一个提示更新此表会导致超时错误:

mysql> update users set userID = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

【问题讨论】:

  • 如果@x 随后设置为 NULL 会发生什么? (并且我假设它在交易中?)
  • 将@x设置为null并没有什么不同,是的,它在一个事务中(使用未提交的事务隔离级别)
  • MySQL 已确认这是一个错误:bugs.mysql.com/bug.php?id=67452

标签: mysql locking transaction-isolation read-uncommitted


【解决方案1】:

不管怎样,这个锁定不限于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
...

正如您在记录的错误Bug #67452 Setting a variable from a select acquires a lock when using read uncommitted 中所讨论的,这种行为可能是设计使然。它似乎与 SELECT 语句属于同一类别,其结果用于修改数据,例如描述的这些案例:

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

当在构造 REPLACE INTO t SELECT ... FROM s WHERE ...UPDATE t ... WHERE col IN (SELECT ... FROM s ...) 中使用 SELECT 时,InnoDB 会对表 s 中的行设置共享的 next-key 锁。

next-key locks的原因是为了让SELECT的结果更稳定。也就是说,我们不希望SELECT 匹配的行在用于UPDATE 或其他数据修改语句时发生变化。

即使 tx_isolation 是 REPEATABLE-READ,这也很重要,因为当 SELECT 语句作为任何类型的 UPDATE 的一部分执行时,InnoDB 不支持 REPEATABLE-READ


你的评论:

无论文档如何,都会发生以下情况:

当你执行普通的SELECT 语句时,InnoDB 不会锁定任何东西,除了SERIALIZABLE 之外的任何事务隔离。

如果您执行SELECT ... LOCK IN SHARE MODESELECT ... FOR UPDATE,它当然会锁定。

但是,当您将 SELECT 作为数据修改语句的一部分(如 INSERT INTO...SELECT)或在 UPDATE 的子查询中或在 SET @variable := (SELECT...) 中找到时,它使用共享锁来确保更新过程中数据不会改变。

文档可能不完整。更好地测试。

【讨论】:

  • 对于可重复读取,MySQL 将获得选择语句的锁(我对此没有任何问题,因为这是人们期望的可重复读取完成的方式),这一点既明确又明确。如果您点击您引用的链接,您将看到它链接到dev.mysql.com/doc/refman/5.6/en/innodb-locks-set.html,其中指出“未提交读:SELECT 语句以非锁定方式执行。”,因此我的未提交读问题不属于该类别。在谈论可重复读取时,即使逻辑不起作用,因为根据定义,它会进行更多锁定。
【解决方案2】:

您的第一条语句在表上执行 SELECT,因此事务在一行上获得了读锁。

第二个事务尝试在同一个表上获取写锁(在所有行上,因为没有WHERE 子句),但不能。

您需要在SET @x = (...) 之后发出COMMIT(或ROLLBACK)命令,以便它释放读锁。

上面写错了。我保留这篇文章只是因为下面的 cmets 可能会感兴趣。

【讨论】:

  • 但是第一条语句的选择不应该阻止对该表的更新。 MySQL真的那么糟糕吗?
  • @a_horse_with_no_name 好吧,根据定义,READ 锁可以防止并发更新,我不认为这是 MySQL 特有的。我必须在问题中遗漏一些东西(另一方面,几个事务可能会同时获得 READ 锁,但这是另一回事)。当然,我假设SET @x=(SELECT) 语句是在事务中运行的(否则问题不应该出现)。
  • Oracle、Postgres、Firebird、DB2(任何使用 MVCC 的 DBMS)不会阻止并发读写。问题的顺序在那里可以很好地工作(我总是在自动提交关闭的情况下运行)
  • @a_horse_with_no_name 我的错。如果只发出SELECT (...) 语句,MySQL 也不会出现此问题。它只发生在SET @x = (SELECT... ) 语句中。谢谢你纠正我。我误读了这个问题。
  • 我对此做了一些测试,这似乎是 DML 语句中子选择的普遍问题。 update foo set some_value = 'bar' where id in (select id from users) 也会阻止对 users(!) 表的写入,即使它根本没有更新。
猜你喜欢
  • 1970-01-01
  • 2011-01-02
  • 1970-01-01
  • 2015-05-20
  • 1970-01-01
  • 2020-12-17
  • 2013-04-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多