请帮助我理解 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.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_COMMITTED
和 SELECT ... FOR UPDATE
?
答案应该是可移植的(不是特定于数据库的)。如果这不可能,请解释原因。
原文由 Gili 发布,翻译遵循 CC BY-SA 4.0 许可协议
实现房间和标签之间的一致性并确保房间在被删除后永远不会返回的唯一可移植方法是使用
SELECT FOR UPDATE
锁定它们。然而,在某些系统中,锁定是并发控制的副作用,您无需明确指定
FOR UPDATE
即可获得相同的结果。这取决于您的数据库系统正在使用的并发控制。
MyISAM
inMySQL
(和其他几个旧系统)在查询期间确实锁定了整个表。在
SQL Server
,SELECT
查询在他们检查过的记录/页面/表上放置共享锁,而DML
查询放置更新锁或降级为共享锁)。独占锁与共享锁不兼容,因此SELECT
或DELETE
查询将锁定直到另一个会话提交。In databases which use
MVCC
(likeOracle
,PostgreSQL
,MySQL
withInnoDB
), aDML
查询创建记录的副本(以一种或另一种方式),通常读者不会阻止作者,反之亦然。 For these databases, aSELECT FOR UPDATE
would come handy: it would lock eitherSELECT
or theDELETE
query until another session commits, just asSQL Server
确实。一般
REPEATABLE READ
不禁止虚行(行在另一个事务中出现或消失,而不是被修改)在
Oracle
及更早的PostgreSQL
版本中,REPEATABLE READ
实际上是SERIALIZABLE
的同义词。基本上,这意味着事务在开始后看不到所做的更改。因此,在此设置中,最后一个Thread 1
查询将返回房间,就好像它从未被删除一样(这可能是您想要的,也可能不是您想要的)。如果您不想在房间被删除后显示房间,您应该使用SELECT FOR UPDATE
锁定行In
InnoDB
,REPEATABLE READ
andSERIALIZABLE
are different things: readers inSERIALIZABLE
mode set next-key locks on the records they evaluate, effectively防止它们同时DML
。因此,您不需要SELECT FOR UPDATE
在可序列化模式下,但在REPEATABLE READ
或READ COMMITED
中确实需要它们。请注意,隔离模式的标准确实规定您在查询中看不到某些怪癖,但没有定义如何(使用锁定或
MVCC
或其他方式)。当我说“您不需要
SELECT FOR UPDATE
”时,我真的应该添加“因为某些数据库引擎实现的副作用”。