使用WHERE IS NULL和LIMIT执行更新时可能发生冲突?

时间:2017-01-13 12:18:51

标签: mysql sql

假设我有下表:

| id | claimed |
----------------
| 1  | NULL    |
| 2  | NULL    |
| 3  | NULL    |

我可以执行此查询来更新其中一行,而不必先执行select。

UPDATE mytable SET claimed = [someId] WHERE claimed IS NULL LIMIT 1

但是,如果发生此查询的两个并发请求会发生什么。后面的请求是否可以覆盖第一个请求的值?我知道发生这种情况的可能性很小,但仍然存在。

1 个答案:

答案 0 :(得分:0)

在事务UPDATE mytable SET claimed = [someId] WHERE claimed IS NULL LIMIT 1中执行语句t1会锁定相应的记录,并阻止任何其他事务t2更新同一记录,直到事务t1提交(或中止)。同时阻止了交易t2; t2一旦t1提交(或中止),t2继续,或者claimed is null在达到超时后自动中止。

授予mysql reference on internal locking methods - row level locking

  

MySQL使用InnoDB表的行级锁定来同时支持   通过多个会话写入访问权限,使其适合   多用户,高度并发和OLTP应用程序。

mysql reference on Locks Set by Different SQL Statements in InnoDB

  

UPDATE ... WHERE ...在每条记录上设置一个独占的下一键锁定   搜索遭遇。但是,只需要索引记录锁定   对于使用唯一索引锁定行以搜索a的语句   独特的一行。

最后锁定在mysql引用InnoDB锁定记录锁的行为:

  

如果事务T1在行r上持有独占(X)锁,则为请求   从某个不同的事务T2中获取r上任一类型的锁   不能立即授予。相反,事务T2必须等待   事务T1释放其对行r的锁定。

只要这两个查询在不同的事务中运行,两个查询就不会获取相同的记录。

请注意,完整记录已被锁定,以便其他事务的其他更新操作被阻止,即使它们会更新相应记录的其他属性。

我尝试使用SequelPro,您可以尝试使用任何您想要的客户端,如下所示:

  1. 确保mytable包含至少两个c1的记录。
  2. 打开两个连接窗口/终端;我们称他们为c2c1
  3. start transaction; UPDATE mytable SET claimed = 15 WHERE claimed IS NULL LIMIT 1; # No commit so far!中,执行以下两个命令:c2
  4. claimed中,执行类似的命令(注意不同的值 start transaction; UPDATE mytable SET claimed = 16 WHERE claimed IS NULL LIMIT 1; # Again, no commit so far):c2
  5. 窗口c1应该通知您它正在工作(即等待 查询完成)。
  6. 切换到窗口commit;并执行命令c2
  7. 切换到窗口commit,其中(先前已启动的)查询应该在此处 现在已经完成;执行mytable;
  8. 在查看claim=15时,一条记录现在应该有claim=16, 另一个应该有db.test.find( { 'values': { $elemMatch: { $elemMatch: { $in: ['a'] } } } }, { "values.$": 1 } )