请帮助我了解SELECT ... FOR UPDATE
背后的用例。
问题1 :以下是应该使用SELECT ... FOR UPDATE
的一个很好的例子吗?
假设:
应用程序想要列出所有房间及其标签,但需要区分没有标签的房间与已移除的房间。如果未使用SELECT ... FOR UPDATE,可能发生的情况是:
[id = 1]
[id = 1, name = 'cats']
[room_id = 1, tag_id = 1]
SELECT id FROM rooms;
returns [id = 1]
DELETE FROM room_tags WHERE room_id = 1;
DELETE FROM rooms WHERE id = 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_COMMITTED
进行SELECT ... FOR UPDATE
?
答案应该是可移植的(不是特定于数据库的)。如果那是不可能的,请解释原因。
答案 0 :(得分:66)
实现房间和标签之间一致性以及确保房间在删除后永远不会返回的唯一便携方式是使用SELECT FOR UPDATE
锁定它们。
但是在某些系统中,锁定是并发控制的副作用,您可以在不明确指定FOR UPDATE
的情况下获得相同的结果。
要解决此问题,线程1应
SELECT id FROM rooms FOR UPDATE
,从而阻止线程2从rooms
删除,直到线程1完成。这是对的吗?
这取决于数据库系统正在使用的并发控制。
MyISAM
MySQL
(以及其他几个旧系统)确实会在查询期间锁定整个表格。
在SQL Server
中,SELECT
查询在他们检查过的记录/页面/表上放置共享锁,而DML
查询放置更新锁(后来被提升为独占)或降级为共享锁)。独占锁与共享锁不兼容,因此SELECT
或DELETE
查询将锁定,直到另一个会话提交。
在使用MVCC
(例如Oracle
,PostgreSQL
,MySQL
和InnoDB
)的数据库中,DML
查询创建记录的副本(以一种或另一种方式)并且通常读者不会阻止写入者,反之亦然。对于这些数据库,SELECT FOR UPDATE
会很方便:它会锁定SELECT
或DELETE
查询,直到另一个会话提交,就像SQL Server
一样。
何时应该使用
REPEATABLE_READ
交易隔离与READ_COMMITTED
SELECT ... FOR UPDATE
进行隔离?
通常,REPEATABLE READ
不禁止幻像行(在另一个事务中出现或消失的行,而不是被修改的行)
在Oracle
及之前的PostgreSQL
版本中,REPEATABLE READ
实际上是SERIALIZABLE
的同义词。基本上,这意味着事务在启动后看不到更改。因此,在此设置中,最后一个Thread 1
查询将返回房间,就好像它从未被删除一样(可能是也可能不是您想要的)。如果您不想在删除后显示房间,则应使用SELECT FOR UPDATE
在InnoDB
中,REPEATABLE READ
和SERIALIZABLE
是不同的事情:SERIALIZABLE
模式下的读者在他们评估的记录上设置了下一键锁,有效地阻止了并发DML
就可以了。因此,您在序列化模式下不需要SELECT FOR UPDATE
,但在REPEATABLE READ
或READ COMMITED
中确实需要它们。
请注意,隔离模式的标准确实规定您在查询中没有看到某些怪癖,但没有定义如何(使用锁定或使用MVCC
或其他方式)。
当我说“你不需要SELECT FOR UPDATE
”时,我真的应该添加“因为某些数据库引擎实现的副作用”。
答案 1 :(得分:21)
简答:
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
影响的行被锁定,其他任何事务都无法以任何方式修改它们,因此,事务隔离级别在这里没有区别。
另请注意,事务隔离级别与锁定无关:设置不同的隔离级别不允许您绕过事务锁定的其他事务中的锁定和更新行。
什么事务隔离级别保证(在不同级别)是事务正在进行时数据的一致性。