隔离级别 vs 乐观锁- Hibernate,JPA

10
我有一个 Web 应用程序,我想通过在试图更新的对象上使用数据库级别锁来确保并发性。我希望确保批量更改、其他用户或进程不会在数据库中引入不一致性。
我看到隔离级别可以确保读取一致性,而乐观锁与 @Version 字段可以确保数据以一致的状态写入。
我的问题是,我们不能仅通过隔离级别来确保一致性吗?通过使更新记录的任何事务可序列化(不考虑性能),我不会确保该事务已采取适当的锁定,并且任何尝试更新或获取锁定或此事务的其他事务将失败吗? 我真的需要版本或时间戳管理来实现这一点吗?
2个回答

13
根据您选择的隔离级别,特定资源将会被锁定,直到事务提交或回滚-这可以是整个表、行或sql块的锁定。这是一种悲观锁定,并且在运行事务时在数据库级别上确保了锁定。
另一方面,乐观锁定假设多个事务很少相互干扰,因此在此方法中不需要锁定。这是一种应用程序端检查,它使用@Version属性来确定在获取记录和尝试更新记录之间版本是否已更改。
在Web应用程序中使用乐观锁定方法是合理的,因为大多数操作跨越多个HTTP请求。通常情况下,您在一个请求中从数据库中提取一些信息,而在另一个请求中进行更新。保持长时间的数据库资源锁定状态非常昂贵且不明智。这就是为什么我们认为没有人会使用我们正在使用的数据集-这更便宜。如果假设被证明是错误的,并且版本已经在他人的请求之间发生了更改,则Hibernate不会更新该行,并将抛出OptimisticLockingException。作为开发人员,您负责管理此情况。
简单示例。在线拍卖服务-您正在查看物品页面。您阅读其说明和规格。这需要5分钟。使用悲观锁定和某些隔离级别,您将阻止其他用户访问此特定项目页面(甚至所有项目!)。在使用乐观锁定后,每个人都可以访问它。阅读有关物品的信息后,您希望对其进行出价,因此单击相应按钮。如果其他任何观看此物品并在此期间更改了其状态(所有者更改了其说明,其他人对其进行出价),则您可能会(取决于应用程序实现)在应用程序接受您的出价之前被通知更改,因为您获得的版本与在数据库中持久化的版本不同。
希望这能为您澄清一些事情。

谢谢您的回答。但是,孤立级别实际上是悲观锁吗?如果我使用孤立级别序列化并启动了事务,那么它们中的一个会被阻塞吗?还是我可以期望Hibernate抛出类似于乐观锁异常的异常? - Dhruv
是的,基本上在您的事务中使用比读取未提交更高的隔离级别,您可以进行悲观锁定。您将锁定您正在使用的资源,无论其他人是否需要它。使用序列化隔离级别,您不会收到任何异常,它只会锁定您正在使用的整个表格,其他事务必须等待才能访问它。 - Piotr Podraza
想象一下我在答案中给你的例子中的REST应用程序。为了让一个用户竞标一个物品,将整个项目表锁定(意思是任何其他用户应用程序都不允许查看任何项目!)是否正确?您必须在代码中区分事务和用户视角中的事务(这通常意味着Web应用程序中的多个请求)。看看谈话记录(http://what-when-how.com/hibernate/implementing-conversations-hibernate/),也许会让事情变得清晰。 - Piotr Podraza
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dhruv
如果不是这样的话,那么是不是无论我们使用什么隔离级别,Hibernate都会始终使用悲观锁定? - Dhruv
2
我不确定你所说的数据库是哪一个,但根据Postgres中Serializable的描述,它实际上更像是乐观锁 - 会引发错误而不是锁定。锁定必须使用SELECT FOR UPDATE来完成。 - Kamil Tomšík

3
除非我们谈论的是一些小型、孤立的 Web 应用程序(只有一个工作在数据库上的应用程序),否则将所有事务设置为可序列化意味着对设计有很高的信心,而不考虑可能不是唯一访问该特定数据库的应用程序。
在我看来,包含 Serializable 隔离级别或悲观锁定,换句话说,应该是经过深思熟虑并应用于:
- 大型数据库和仅更新少数行的短事务 - 两个并发事务修改相同行的机会相对较低。 - 相对长时间运行的事务主要是只读的。
根据我的经验,在大多数情况下,仅使用乐观锁定将是最有益的决策,因为频繁的并发修改通常只发生在少数情况下。乐观锁定肯定也可以帮助其他应用程序运行得更快(不要只考虑自己!)。
因此,当我们考虑悲观 - 乐观锁定策略谱系时,我认为真相更多地倾向于具有一些可序列化特性的乐观锁定。
由于答案基于我在许多复杂的 Web 项目中的个人经验以及我准备 JPA 证书时的笔记,因此我真的无法引用任何内容。
希望这有所帮助。

谢谢你的快速回复!这是否意味着在幕后,ISOLATION LEVEL (SERIALIZABLE) 在开始事务之前实际上会获取一个悲观锁?其他隔离级别特别是 REPEATABLE READ 又如何呢?它也是在行级别上使用悲观锁吗? - Dhruv
当涉及到JPA时,它不允许直接选择隔离级别,除非您取消API的包装并使用供应商特定的配置和提示(该API有时支持)。当您获取悲观锁时,您自动防止不可重复读和幻读。 - Maciej Kowalski
但是Hibernate和Spring允许您在开始事务之前将ISOLATION级别设置为属性。因此,这必须在内部获取事务锁,并且这必须是悲观锁。如果在我的更新方法中我说ISOLATION级别是SERIALIZABLE,那么如果我的事务实际上获得了锁,它不会保证在此期间不可能进行其他更新吗? - Dhruv
你可以在Hibernate中做到这一点,但在JPA中不行(当然最好使用API而不是实现,但这是你的选择)。对于你的问题,答案是肯定的,在使用Serializable隔离级别隔离后,对实体的任何操作都意味着在数据库上获取物理锁,并且在事务提交之前,没有其他事务可以访问该特定行/表。如果其他事务正在处理该行并且您尝试使用Serializable隔离级别访问它,则会收到PessimisticLockException并且您的事务将被回滚(根据JPA规范)。 - Maciej Kowalski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接