“select for update”是否阻止在行不存在时插入其他连接

时间:2010-08-30 15:38:03

标签: sql mysql sql-server oracle concurrency

我对select for update查询是否会锁定不存在的行感兴趣。

e.g。

表FooBar有两列,foo和bar,foo有一个唯一索引

  • 问题查询select bar from FooBar where foo = ? for update
  • 如果查询返回零行
    • 问题查询insert into FooBar (foo, bar) values (?, ?)

现在插入是否会导致索引违规或select for update阻止它?

对SQLServer(2005/8),Oracle和MySQL的行为感兴趣。

5 个答案:

答案 0 :(得分:7)

在Oracle中,SELECT ... FOR UPDATE对不存在的行没有影响(该语句只会引发No Data Found异常)。 INSERT语句将阻止唯一/主键值的重复。尝试插入相同键值的任何其他事务将阻塞,直到第一个事务提交(此时被阻止的事务将获得重复的键错误)或回滚(此时阻塞的事务继续)。

答案 1 :(得分:7)

<强>的MySQL

SELECT ... FOR UPDATE with UPDATE

使用与InnoDB的交易(自动提交已关闭),SELECT ... FOR UPDATE允许一个会话暂时锁定特定记录(或记录),以便其他会话无法更新它。然后,在同一事务中,会话实际上可以在同一记录上执行UPDATE并提交或回滚事务。这将允许您锁定记录,以便其他会话无法更新它,而您可能会执行其他业务逻辑。

这是通过锁定完成的。 InnoDB使用索引来锁定记录,因此锁定现有记录似乎很简单 - 只需锁定该记录的索引即可。

选择... FOR INSDATE更新

但是,要将SELECT ... FOR UPDATEINSERT一起使用,如何锁定尚不存在的记录的索引?如果您使用的默认隔离级别为REPEATABLE READ,InnoDB还将使用 gap 锁。只要您知道要锁定的id(或甚至是id的范围),InnoDB就可以锁定间隙,因此在我们完成之前,不能在该间隙中插入其他记录。

如果您的id列是自动增量列,那么带有SELECT ... FOR UPDATE的{​​{1}}会出现问题,因为您不知道新的INSERT INTO是什么插入它。但是,由于您知道要插入的id,因此id SELECT ... FOR UPDATE将有效。

<强> CAVEAT

在默认隔离级别,不存在的记录上的INSERT 阻止其他事务。因此,如果两个事务都在同一个不存在的索引记录上执行SELECT ... FOR UPDATE,则它们都将获得锁定,并且两个事务都不能更新记录。事实上,如果他们尝试,将会检测到死锁。

因此,如果您不想处理死锁,可以执行以下操作:

插入...

启动交易,然后执行SELECT ... FOR UPDATE。执行业务逻辑,并提交或回滚事务。只要对第一个事务的不存在记录索引执行INSERT,所有其他事务将在尝试INSERT具有相同唯一索引的记录时阻止。如果第二个事务在第一个事务提交插入后尝试插入具有相同索引的记录,那么它将获得“重复键”错误。处理相应的。

选择...锁定共享模式

如果您在INSERT之前选择LOCK IN SHARE MODE,如果之前的交易已插入该记录但尚未提交,则INSERT将阻止,直到上一个交易完成为止。

因此,为了减少重复键错误的可能性,特别是如果在提交或回滚之前执行业务逻辑时保持锁一段时间:

  1. SELECT ... LOCK IN SHARE MODE
  2. 如果没有返回记录,那么
  3. SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE

答案 2 :(得分:2)

在Oracle上:

第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

答案 3 :(得分:1)

我在SQL Server上写了这个东西的详细分析:Developing Modifications that Survive Concurrency

无论如何,你需要使用SERIALIZABLE隔离级别,你真的需要进行压力测试。

答案 4 :(得分:0)

SQL Server仅将FOR UPDATE作为游标的一部分。并且,它仅适用于与游标中当前行关联的UPDATE语句。

因此,FOR UPDATEINSERT没有任何关系。因此,我认为您的答案是它不适用于SQL Server。

现在,可以使用事务和锁定策略模拟FOR UPDATE行为。但是,这可能比你正在寻找的更多。