何时使用SELECT … FOR UPDATE?
请帮我理解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.tag_id = 1 AND tags.id = room_tags.tag_id;
- 返回一个空的列表
现在线程1认为房间1没有标签,但实际上房间已被删除。 为了解决这个问题,线程1应该SELECT id FROM rooms FOR UPDATE
,从而防止线程2从rooms
删除,直到线程1完成。 那是对的吗?
问题2 :什么时候应该使用SELECT ... FOR UPDATE
使用SERIALIZABLE
事务隔离与READ_COMMITTED
?
预计答案是可移植的(不是数据库特定的)。 如果这是不可能的,请解释原因。
实现房间和标签之间一致性的唯一可移植方式,以及确保房间在被删除后永远不会被返回的方式是使用SELECT FOR UPDATE
来locking它们。
但是,在某些系统中,locking是并发控制的一个副作用,并且在没有明确指定FOR UPDATE
情况下获得相同的结果。
为了解决这个问题,线程1应该
SELECT id FROM rooms FOR UPDATE
,从而防止线程2从rooms
删除,直到线程1完成。 那是对的吗?
这取决于您的数据库系统正在使用的并发控制。
-
MySQL
MyISAM
(以及其他一些旧系统)在查询期间locking整个表。 -
在
SQL Server
,SELECT
查询将共享锁放置在他们所检查的logging/页面/表上,而DML
查询放置更新锁(稍后被提升为独占或降级到共享锁)。 排他锁与共享锁不兼容,所以SELECT
或DELETE
查询将locking,直到另一个会话提交。 -
在使用
MVCC
数据库(如Oracle
,PostgreSQL
,MySQL
与InnoDB
)中,DML
查询创buildlogging的副本(通过一种或另一种方式),通常读者不会阻止写入者,反之亦然。 对于这些数据库,一个SELECT FOR UPDATE
会派上用场:它会lockingSELECT
或DELETE
查询,直到另一个会话提交,就像SQL Server
一样。
什么时候应该使用
REPEATABLE_READ
事务隔离与使用SELECT ... FOR UPDATE
READ_COMMITTED
?
通常, REPEATABLE READ
不禁止幻影行(在另一个事务中出现或消失的行,而不是被修改)
-
在
Oracle
和早期的PostgreSQL
版本中,REPEATABLE READ
实际上是SERIALIZABLE
的同义词。 基本上,这意味着交易没有看到启动后所做的更改。 所以在这个设置中,最后一个Thread 1
查询将返回房间,就好像它从未被删除过(这可能是也可能不是你想要的)。 如果您不想在删除房间后显示房间,则应使用SELECT FOR UPDATE
locking行 -
在
InnoDB
,REPEATABLE READ
和SERIALIZABLE
是不同的事情:在SERIALIZABLE
模式下的读者在他们评估的logging上设置了下一个键锁,有效地防止了他们的并发DML
。 所以你不需要在可序列化模式下进行SELECT FOR UPDATE
,但是在REPEATABLE READ
或READ COMMITED
需要它们。
请注意,隔离模式的标准确实规定您在查询中没有看到某些怪癖,但没有定义如何(通过locking或使用MVCC
或其他方式)。
当我说“你不需要SELECT FOR UPDATE
”时,我确实应该添加“由于某些数据库引擎执行的副作用”。
简短的答案:
Q1:是的。
问题2:你使用哪个并不重要。
很长的回答:
一个select ... for update
将会(正如它暗示的)select某些行,同时也将它们locking,就好像它们已经被当前事务更新一样(或者如同已经执行了身份更新一样)。 这允许您在当前事务中再次更新它们,然后进行提交, 而不用另外的事务以任何方式修改这些行。
看看它的另一种方式,就好像以下两个语句是自动执行的:
select * from my_table where my_condition; update my_table set my_column = my_column where my_condition;
由于受my_condition
影响的行被locking,所以其他事务不能以任何方式修改它们,因此事务隔离级别在这里没有任何区别。
另请注意,事务隔离级别与locking无关:设置不同的隔离级别不允许您绕过locking和更新由事务locking的其他事务中的行。
事务隔离级别(不同级别)所保证的是数据在事务处理过程中的一致性。