【问题标题】:When to use SELECT ... FOR UPDATE?何时使用 SELECT ... FOR UPDATE?
【发布时间】:2012-06-11 17:54:03
【问题描述】:

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

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

给定:

  • 房间[id]
  • 标签[id, name]
  • 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_COMMITTEDSELECT ... FOR UPDATE

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

【问题讨论】:

  • 您使用的是哪个 RDBMS?
  • @Quassnoi,如问题底部所述,我正在寻找一种可移植(非特定于数据库)的解决方案。
  • REPEATABLE_READREAD_COMMITTED 选项是可移植选项吗?我得到的唯一结果是 MSSQL 服务器
  • @BillyONeal:请注意,隔离模式保证您不会看到他们不允许的怪癖,但不要说他们允许的怪癖。这意味着设置,比如说,READ COMMITTED 模式并没有定义你是否会真正看到另一个事务提交的记录:它只是确保你永远不会看到未提交的记录。
  • select ... for update on rooms 仍将允许删除 room_tags,因为它们是单独的表。您的意思是问for update 子句是否会阻止删除rooms

标签: mysql sql sql-server transactions select-for-update


【解决方案1】:

实现房间和标签之间的一致性并确保房间在被删除后永远不会返回的唯一可移植方法是使用SELECT FOR UPDATE 锁定它们。

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


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

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

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

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

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

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

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

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

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

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

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

【讨论】:

  • 最后一点是问题的症结所在,我认为:“在可序列化模式下不需要 SELECT FOR UPDATE,但在 REPEATABLE READ 或 READ COMMITED 中确实需要它们”。
  • 你是对的。第二个问题应该问什么时候应该使用SERIALIZABLE,而不是READ_COMMITTEDSELECT ... FOR UPDATE。您能否更新您的答案以反映这个更新后的问题?
  • @Gili:“在可序列化模式下,您不需要SELECT FOR UPDATE”,使用InnoDB。对于其他MVCC 系统,两者是同义词,您确实需要SELECT FOR UPDATE
  • 我发现 Colin 的 post 比您的回答更能回答我的具体问题,但我感谢您提供的所有参考资料。我会接受一个最能结合两者的答案(具体答案在上面,下面的支持参考)。
  • This depends on the concurrency control your database system is using: 我觉得你在扯皮。您在下面列出的所有情况都表明在SELECT 到事务结束之间没有删除房间。那么,答案不应该只是Yes 以及下面的支持参考吗?
【解决方案2】:

简短回答:

Q1:是的。

Q2:不管你用哪个。

长答案:

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

换个角度看,好像下面两条语句是原子执行的:

select * from my_table where my_condition;

update my_table set my_column = my_column where my_condition;

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

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

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

【讨论】:

  • 我认为What transaction isolation levels do guarantee [...] is the consistency of data once transactions are completed. 错误地暗示隔离级别不会影响事务中发生的事情。我建议修改此部分,并提供有关它们如何影响您在交易期间看到(或未看到)的更多详细信息。
  • 我发现您的帖子比Quassnoi's 更能回答我的具体问题,但我感谢他提供的所有参考资料。我会接受一个最能结合两者的答案(上面的具体答案,下面的支持参考)。
  • 锁定和隔离可以互换复杂。那么有什么书可以了解这方面的知识吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-28
  • 2016-03-30
  • 2019-06-13
  • 2014-11-29
  • 2015-03-08
  • 2020-07-28
  • 1970-01-01
相关资源
最近更新 更多