当行不存在时,“SELECT FOR UPDATE”是不是会阻止其他连接插入?
Posted
技术标签:
【中文标题】当行不存在时,“SELECT FOR UPDATE”是不是会阻止其他连接插入?【英文标题】:Does "SELECT FOR UPDATE" prevent other connections inserting when the row is not present?当行不存在时,“SELECT FOR UPDATE”是否会阻止其他连接插入? 【发布时间】:2011-04-05 20:07:52 【问题描述】:我对@987654321@ 查询是否会锁定不存在的行感兴趣。
示例
表 FooBar
有两列,foo
和 bar
,foo
有一个唯一索引。
SELECT bar FROM FooBar WHERE foo = ? FOR UPDATE
如果第一个查询返回零行,请发出查询INSERT INTO FooBar (foo, bar) values (?, ?)
现在INSERT
是否有可能导致索引冲突,或者SELECT FOR UPDATE
会阻止这种情况发生吗?
对 SQLServer (2005/8)、Oracle 和 mysql 上的行为感兴趣。
【问题讨论】:
MSSQL 甚至支持这种语法吗? 它有“with updlock”,我相信这几乎是一样的。 【参考方案1】:MySQL
SELECT ... FOR UPDATE with UPDATE
使用 InnoDB 事务(关闭自动提交),SELECT ... FOR UPDATE
允许一个会话临时锁定特定记录(或记录),以便其他会话无法更新它。然后,在同一事务中,会话实际上可以对同一记录执行UPDATE
并提交或回滚该事务。这将允许您锁定记录,以便在您执行一些其他业务逻辑时没有其他会话可以更新它。
这是通过锁定来完成的。 InnoDB 利用索引来锁定记录,因此锁定现有记录似乎很容易——只需锁定该记录的索引即可。
SELECT ... FOR UPDATE with INSERT
但是,要将SELECT ... FOR UPDATE
与INSERT
一起使用,如何为尚不存在的记录锁定索引?如果您使用REPEATABLE READ
的默认隔离级别,InnoDB 还将使用 gap 锁。只要您知道要锁定的 id
(甚至是 id 范围),InnoDB 就可以锁定间隙,因此在我们完成之前不能在该间隙中插入其他记录。
如果您的 id
列是自动增量列,那么带有 INSERT INTO
的 SELECT ... FOR UPDATE
会出现问题,因为在插入之前您不会知道新的 id
是什么。但是,由于您知道要插入的 id
,因此 SELECT ... FOR UPDATE
和 INSERT
将起作用。
警告
在默认隔离级别上,不存在的记录上的SELECT ... FOR UPDATE
不会阻止其他事务。因此,如果两个事务都对同一个不存在的索引记录执行SELECT ... FOR UPDATE
,它们都会获得锁,并且两个事务都无法更新记录。事实上,如果他们尝试,就会检测到死锁。
因此,如果您不想处理死锁,您可能只需执行以下操作:
插入...
开始一个事务,并执行INSERT
。执行您的业务逻辑,并提交或回滚事务。一旦您在第一个事务的不存在的记录索引上执行INSERT
,如果所有其他事务尝试INSERT
具有相同唯一索引的记录,它们将被阻止。如果第二个事务在第一个事务提交插入后尝试插入具有相同索引的记录,那么它将得到“重复键”错误。相应处理。
选择...锁定共享模式
如果您在INSERT
之前选择LOCK IN SHARE MODE
,如果之前的事务已插入该记录但尚未提交,则SELECT ... LOCK IN SHARE MODE
将阻塞直到之前的事务完成。
因此,为了减少重复键错误的机会,特别是如果您在执行业务逻辑时持有锁一段时间,然后再提交或回滚:
SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE
如果没有返回记录,那么
INSERT INTO FooBar (foo, bar) VALUES (?, ?)
【讨论】:
通常是正确的,但 SELECT ... FOR UPDATE 与 INSERT 恕我直言,至少在 MySQL 中是没用的。请查看here 以获得解释。 关于警告:如果我对不存在的记录执行SELECT ... FOR UPDATE
,它绝对确实锁定!事实上,它似乎锁定了整个索引(或整个表 - 不确定)。我读到这取决于用于查找行的索引(如果有)。用 MySQL 5.7.25 测试。
您提到的警告的另一个解决方法是将隔离级别设置为READ COMMITTED
,这会阻止 MySQL 设置间隙锁。这适用于我的用例,但对其他人来说,他们可能想研究这个隔离级别,看看这是否是最好的解决方案。【参考方案2】:
我在SQL Server上写过详细分析这个东西:Developing Modifications that Survive Concurrency
无论如何,你需要使用SERIALIZABLE隔离级别,你确实需要压力测试。
【讨论】:
【参考方案3】:在甲骨文上:
会话 1
create table t (id number);
alter table t add constraint pk primary key(id);
SELECT *
FROM t
WHERE id = 1
FOR UPDATE;
-- 0 rows returned
-- this creates row level lock on table, preventing others from locking table in exclusive mode
第 2 场
SELECT *
FROM t
FOR UPDATE;
-- 0 rows returned
-- there are no problems with locking here
rollback; -- releases lock
INSERT INTO t
VALUES (1);
-- 1 row inserted without problems
【讨论】:
我认为SELECT FOR UPDATE
不会在任何一个会话中阻止INSERT
,只会阻止UPDATE
。【参考方案4】:
在 Oracle 中,SELECT ... FOR UPDATE 对不存在的行没有影响(该语句只会引发 No Data Found 异常)。 INSERT 语句将防止重复唯一/主键值。任何其他尝试插入相同键值的事务都将阻塞,直到第一个事务提交(此时被阻塞的事务将得到重复键错误)或回滚(此时被阻塞的事务继续)。
【讨论】:
我将重申关于“该语句仅引发未找到数据的异常”的部分; SELECT...FOR UPDATE 可以返回行,但仍然对不存在的行没有影响。 ;) 不是在 OP 声明的情况下,选择在 foo=explicit_value 上,foo 被唯一索引。 SELECT 语句可能返回 0 行。只有在使用 INTO 调用时才会引发 NO_DATA_FOUND。 点了。我正在查看伪代码并转换为程序上下文。 Oracle 使用什么事务隔离?这是在快照隔离下吗?【参考方案5】:SQL Server 仅将 FOR UPDATE
作为游标的一部分。而且,它仅适用于与游标中的当前行关联的 UPDATE 语句。
因此,FOR UPDATE
与 INSERT
没有任何关系。因此,我认为您的答案是它不适用于 SQL Server。
现在,可以使用事务和锁定策略来模拟FOR UPDATE
行为。但是,这可能超出了您的预期。
【讨论】:
从历史上看,行为可能有所不同(当更新锁定整个表时) - 但在过去十年中没有(很可能,自从引入 FOR UPDATE 以来就没有)。 我认为 OP 会指的是WITH (UPDLOCK)
然后......以上是关于当行不存在时,“SELECT FOR UPDATE”是不是会阻止其他连接插入?的主要内容,如果未能解决你的问题,请参考以下文章
仅当行不存在时,如何在 SQL Server 中执行插入操作? [复制]