假设我有下表:
| id | claimed |
----------------
| 1 | NULL |
| 2 | NULL |
| 3 | NULL |
我可以执行这个查询来准确地更新(任何)一行,而不必先执行select。
UPDATE mytable SET claimed = [someId] WHERE claimed IS NULL LIMIT 1
但是,如果此查询的两个并发请求发生时会发生什么情况。以后的请求是否可以重写第一个请求的值?我知道发生这种事的可能性很小,但仍然存在。
最佳答案
在事务UPDATE mytable SET claimed = [someId] WHERE claimed IS NULL LIMIT 1
中执行语句t1
将锁定相应的记录,并防止任何其他事务t2
在事务t1
提交(或中止)之前更新同一记录。同时,事务t2
被阻塞;t2
在提交(或中止)后继续,或在达到超时时自动中止。
授予:
mysql对innodb表使用行级锁定来支持同时
通过多个会话进行写访问,使它们适合于
多用户、高并发和oltp应用程序。
以及mysql reference on internal locking methods - row level locking:
更新。。。哪里。。。在每个记录上设置独占的下一个密钥锁
搜索遇到了。但是,只需要索引记录锁
对于使用唯一索引锁定行以搜索
唯一的行。
最后,mysql中的锁定行为引用innodb locking for record locks:
如果事务T1持有R行的独占(X)锁,则请求
对于r上任意类型的锁,从某个不同的事务t2
无法立即授予。相反,事务t2必须等待
事务T1释放对行R的锁。
因此,只要这两个查询在不同的事务中运行,这两个查询就不会获取相同的记录。
注意,完整的记录被锁定,这样其他事务的其他更新操作被阻止,即使它们会更新相应记录的其他属性。
我用Sequelpro试过了,你可以和任何你想要的客户一起试,如下所示:
确保mytable至少包含两个t1
记录。
打开两个连接窗口/终端;我们称它们为t2
和claimedis null
在c1
中,执行以下两个命令:c2
在c1
中,执行类似的命令(注意start transaction;UPDATE mytable SET claimed = 15 WHERE claimed IS NULL LIMIT 1; #No commit so far!
):c2
窗口应通知您它正在工作(即等待
要完成的查询)。
切换到窗口claimed
并执行命令start transaction; UPDATE mytable SET claimed = 16 WHERE claimed IS NULL LIMIT 1; # Again, no commit so far
切换到窗口c2
,其中(先前启动的)查询应
现在已经完成;执行c1
;
当查看commit;
时,一条记录现在应该有c2
,
另一个应该有commit
。
关于mysql - 使用WHERE IS NULL和LIMIT执行更新时可能发生冲突?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41634516/