147

请帮助我了解背后的用例SELECT ... FOR UPDATE

问题 1SELECT ... FOR UPDATE :以下是什么时候应该使用的一个很好的例子吗?

鉴于:

  • 房间[id]
  • 标签[id,名称]
  • room_tags[room_id, tag_id]
    • room_id 和 tag_id 是外键

该应用程序想要列出所有房间及其标签,但需要区分没有标签的房间和已删除的房间。如果不使用 SELECT ... FOR UPDATE,可能发生的情况是:

  • 最初:
    • 房间包含[id = 1]
    • 标签包含[id = 1, name = 'cats']
    • room_tags 包含[room_id = 1, tag_id = 1]
  • 线程 1:SELECT id FROM rooms;
    • returns [id = 1]
  • 线程 2:DELETE FROM room_tags WHERE room_id = 1;
  • 线程 2:DELETE FROM rooms WHERE id = 1;
  • 线程 2:[提交事务]
  • 线程 1:SELECT tags.name FROM room_tags, tags WHERE room_tags.room_id = 1 AND tags.id = room_tags.tag_id;
    • 返回一个空列表

现在线程 1 认为房间 1 没有标签,但实际上房间已被删除。为了解决这个问题,线程 1 应该SELECT id FROM rooms FOR UPDATE,从而防止线程 2 删除,rooms直到线程 1 完成。那是对的吗?

问题 2:什么时候应该使用SERIALIZABLE事务隔离而不是READ_COMMITTEDwith SELECT ... FOR UPDATE

答案应该是可移植的(不是特定于数据库的)。如果这不可能,请解释原因。

4

2 回答 2

109

实现房间和标签之间的一致性并确保房间在被删除后永远不会返回的唯一便携方法是使用SELECT FOR UPDATE.

但是,在某些系统中,锁定是并发控制的副作用,您无需FOR UPDATE明确指定即可获得相同的结果。


为了解决这个问题,线程 1 应该SELECT id FROM rooms FOR UPDATE,从而防止线程 2 删除,rooms直到线程 1 完成。那是对的吗?

这取决于您的数据库系统正在使用的并发控制。

  • MyISAMin MySQL(和其他几个旧系统)在查询期间确实锁定了整个表。

  • SQL Server中,SELECT查询在他们检查过的记录/页面/表上放置共享锁,而DML查询放置更新锁(后来升级为独占锁或降级为共享锁)。排他锁与共享锁不兼容,因此SELECTorDELETE查询将锁定直到另一个会话提交。

  • 在使用MVCC(如Oracle, PostgreSQL, MySQLwith InnoDB)的数据库中,DML查询会创建记录的副本(以一种或另一种方式),通常读者不会阻止作者,反之亦然。对于这些数据库, aSELECT FOR UPDATE会派上用场:它会锁定其中一个SELECTDELETE查询,直到另一个会话提交,就像这样SQL Server做一样。

什么时候应该使用REPEATABLE_READ事务隔离而不是READ_COMMITTEDwith SELECT ... FOR UPDATE

通常,REPEATABLE READ不禁止幻像行(在另一个事务中出现或消失的行,而不是被修改的行)

  • Oracle和更早的PostgreSQL版本中,REPEATABLE READ实际上是SERIALIZABLE. 基本上,这意味着事务在开始后看不到所做的更改。所以在这个设置中,最后一个Thread 1查询将返回房间,就好像它从未被删除一样(这可能是也可能不是你想要的)。如果您不想在房间被删除后显示房间,您应该使用锁定行SELECT FOR UPDATE

  • InnoDB,REPEATABLE READSERIALIZABLE中是不同的东西:模式中的读者在SERIALIZABLE他们评估的记录上设置下一个键锁,有效地防止DML它们的并发。因此,您不需要SELECT FOR UPDATEin 可序列化模式,但确实需要它们 in REPEATABLE READor READ COMMITED

请注意,隔离模式的标准确实规定您在查询中看不到某些怪癖,但没有定义如何(使用锁定或使用MVCC或其他方式)。

当我说“你不需要SELECT FOR UPDATE”时,我真的应该添加“因为某些数据库引擎实现的副作用”。

于 2013-05-07T18:00:13.857 回答
43

简短的答案:

Q1:是的。

Q2:不管你用哪个。

长答案:

Aselect ... for update将(正如它所暗示的那样)选择某些行但也会锁定它们,就好像它们已经被当前事务更新(或者好像已经执行了身份更新)。这允许您在当前事务中再次更新它们然后提交,而另一个事务无法以任何方式修改这些行。

另一种看待它的方式,就好像以下两条语句是原子执行的:

select * from my_table where my_condition;

update my_table set my_column = my_column where my_condition;

由于受影响的行my_condition被锁定,没有其他事务可以以任何方式修改它们,因此,事务隔离级别在这里没有区别。

另请注意,事务隔离级别独立于锁定:设置不同的隔离级别不允许您绕过锁定和更新由您的事务锁定的不同事务中的行。

事务隔离级别(在不同级别)所保证的是事务进行时数据的一致性。

于 2013-05-07T18:00:34.693 回答