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

Mik*_*e Q 15 mysql sql sql-server oracle concurrency

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

例如

表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的行为感兴趣.

Mar*_*ams 9

MySQL的

SELECT ... FOR UPDATE with UPDATE

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

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

使用INSERT选择... FOR UPDATE

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

如果你的id列是一个自动增量列,然后SELECT ... FOR UPDATEINSERT INTO会产生问题,因为你不知道的新东西id是直到你插入它.不过,既然你知道了id,你要插入,SELECT ... FOR UPDATEINSERT将工作.

警告

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

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

插入 ...

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

选择...锁定共享模式

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

因此,为了减少重复键错误的可能性,尤其是在执行业务逻辑之前保持锁定一段时间,然后再提交或回滚它们:

  1. SELECT bar FROM FooBar WHERE foo = ? LOCK FOR UPDATE
  2. 如果没有返回记录,那么
  3. INSERT INTO FooBar (foo, bar) VALUES (?, ?)

  • 关于警告:如果我对一个不存在的记录执行`SELECT ... FOR UPDATE`,它绝对_does_锁定!事实上,它似乎锁定了整个索引(或整个表 - 不确定)。我读到这取决于用于查找行的索引(如果有)。用 MySQL 5.7.25 测试。 (4认同)

DCo*_*kie 7

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