何时使用SELECT…FOR UPDATE?


118

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

问题1:以下是何时SELECT ... 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.tag_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

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


2
您正在使用哪个RDBMS?
Quassnoi

2
正如问题底部提到的,@ Quassnoi,我正在寻找一种便携式(非特定于数据库的)解决方案。
吉利

2
是选件REPEATABLE_READREAD_COMMITTED还是便携式选件?唯一的结果我得到的那些是MSSQL服务器
比利·奥尼尔

3
@BillyONeal:请注意,隔离模式可确保您看不到它们不允许的怪癖,但对它们允许的怪癖不做任何说明。这意味着设置(例如,READ COMMITTED模式)并没有定义您是否将实际看到另一个事务提交的记录:它只是确保您永远不会看到未提交的记录。
Quassnoi

3
select ... for updaterooms将仍然允许room_tags被删除,因为它们是独立的表。您是否要问该for update条款是否会阻止从中删除rooms
克里斯·萨克森

Answers:


83

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

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


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

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

  • MyISAMMySQL(和其他几个老系统)则锁定整个表进行查询的时间。

  • 在中SQL ServerSELECT查询将共享锁放置在已检查的记录/页面/表上,而DML查询将更新锁放置(随后将其升级为互斥或降级为共享锁)。互斥锁与共享锁不兼容,因此SELECTDELETE查询将锁定,直到提交另一个会话为止。

  • 在数据库在使用MVCC(如OraclePostgreSQLMySQLInnoDB),一个DML查询创建记录的副本(以一种或另一种方式),一般读者不会阻止作家,反之亦然。对于这些数据库,a 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 READ并且SERIALIZABLE是不同的东西:读者SERIALIZABLE模式设置上,他们评估记录next-key锁定,有效防止并发DML他们。因此,您不需要SELECT FOR UPDATE处于可序列化模式,而需要在REPEATABLE READ或中使用它们READ COMMITED

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

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


1
最后一点是问题的症结,我认为:“您不需要在可序列化模式下进行SELECT FOR UPDATE,但确实需要在REPEATABLE READ或READ COMMITED中使用它们”。
科林·哈特

你是对的。第二个问题应该问时SERIALIZABLE,应使用与READ_COMMITTEDSELECT ... FOR UPDATE。您能否更新您的答案以反映此更新的问题?
吉利

1
@Gili:使用,“您不需要SELECT FOR UPDATE处于可序列化模式” InnoDB。在其他MVCC系统中,这两个是同义词,您确实需要SELECT FOR UPDATE
Quassnoi

1
我发现Colin的帖子比您的答案更好地回答了我的特定问题,但我感谢您提供的所有参考。我将接受一个将两者完美结合的答案(最上面的特定答案,下面是支持的参考文献)。
吉利

This depends on the concurrency control your database system is using:我想你是在梳头。您在下面列出的所有情况都表明SELECT,在交易结束之前并未删除会议室。因此,答案不应该仅仅Yes与下面的支持参考文献相提并论吗?
吉利

33

简短的答案:

问题1:是的。

问题2:使用哪个都没关系。

长答案:

select ... for update会(因为这意味着)选择特定的行,但也锁住他们,如果他们已经被当前事务(如果被执行的身份更新已为)更新。这使您可以在当前事务中再次更新它们,然后提交,而另一个事务不能以任何方式修改这些行。

换一种方式来看,就好像以下两个语句是原子执行的:

select * from my_table where my_condition;

update my_table set my_column = my_column where my_condition;

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

还要注意,事务隔离级别与锁定无关:设置不同的隔离级别不允许绕开锁定并更新由事务锁定的其他事务中的行。

事务隔离级别(在不同级别)可以保证的是,在事务进行过程中数据的一致性。


1
我认为What transaction isolation levels do guarantee [...] is the consistency of data once transactions are completed.错误地暗示隔离级别不会影响事务期间发生的事情。我建议修改本节,并提供有关它们如何影响交易过程中您看到(或看不到)的更多详细信息。
吉利

1
我发现您的帖子比Quassnoi的答案更好地回答了我的特定问题,但我感谢他提供的所有参考。我将接受一个将两者完美结合的答案(最上面的特定答案,下面是支持的参考文献)。
吉利

锁定和隔离是可互换地复杂的。那么,有没有书籍可以使您对此有所了解?
Chao
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.