book:
id: primary key, integer
title: varchar
borrowed: boolean
borrowed_by_user_id: foreign key user.id
user:
id: primary key, integer
name: varchar
blocked: boolean
隔离级别是READ COMMITED,因为它是PostgreSQL中的默认级别(此要求不是来自我)。
我正在使用一个数据库事务来SELECT FOR UPDATE
一本书,如果还没有借书的话,可以借给任何用户。这本书被选中FOR UPDATE
,所以不能同时借用。
但还有另一个问题。我们不允许向被阻止的用户出借书籍。我们怎样才能确定?即使我们在开始时检查用户是否未被阻止,结果可能也不正确,因为并发事务可能会在检查后阻止用户。
例如,管理员面板中的并发事务可以阻止用户。
如何解决这个问题?
SERIALIZABLE
。它需要处理错误,是吗?CHECK
是如何工作的。你能谈谈更多吗?答案 0 :(得分:0)
这实际上是两个问题。
关于图书:
如果您在考虑借出书籍后立即用TestPropertyValues
锁定该书,这就是“悲观锁定”的一个例子,并会阻止该书进行所有并发活动。
如果交易非常短,那很好 - 特别是,如果锁定和交易结束之间没有用户交互。
否则你应该使用“乐观锁定”。这可以通过以下几种方式完成:
使用SELECT ... FOR UPDATE
事务隔离。然后更新自您读取数据后已修改的图书将导致序列化错误(请参阅末尾的注释)。
选择图书时,请记住系统列REPEATABLE READ
和ctid
的值。然后更新如下:
xmin
如果没有更新行,有人必须在你查看之后修改过这本书。
关于用户:
三个想法:
您使用UPDATE books SET ...
WHERE id = ...
AND ctid = original_ctid AND xmin = original_xmin;
事务隔离(请参阅末尾的说明)。
您在用户上维护一个包含用户借阅的图书数量的计数器。
然后你可以有一个像
这样的检查约束SERIALIZABLE
这样的检查约束在每个语句的末尾进行评估,并且必须产生ALTER TABLE users ADD CHECK (NOT blocked OR books_borrowed = 0);
,否则会引发错误。
因此,借用书籍的交易或阻止用户的交易必须失败(两个交易都必须修改用户)。
在将图书借给用户之前,您运行
TRUE
如果您获得TRUE,则中止交易,否则借出书籍。
想要阻止用户的并发事务也需要SELECT blocked FROM users WHERE id = ... FOR UPDATE;
用户,仅检查是否有任何图书借给该用户。
这样,就不会发生任何不一致:如果你想阻止一个用户,所有想要向用户出借书籍的并发交易必须要完成,以便你看到它们的效果,或者它们必须等到你完成阻止用户,然后他们将失败。
请注意更高的隔离级别:
如果您以SELECT ... FOR UPDATE
或REPEATABLE READ
的隔离级别运行事务,则可能会遇到序列化错误。这些不是程序中的错误,它们是正常的并且是预期的。如果遇到序列化错误,则必须回滚并再次尝试相同的事务。这是你不用担心竞争条件而付出的代价。