什么是丢失更新?
当两个并发事务同时更新相同的数据库记录/列时,导致第一个更新被第二个事务以静默方式覆盖。数据库中的这种现象被称为经典的更新丢失问题。以下是丢失更新时的事件顺序-
当两个人尝试更新 JIRA 中的相同 Bug 时,会发生空中碰撞,JIRA 会优雅地处理它。JIRA的程序员知道如何防止丢失更新!
处理此场景的方法主要有两种:
-
乐观锁定
-
悲观锁定
我们将通过适当的示例一一讨论这两种方法,
乐观锁定方法
所有用户/线程都可以同时读取数据,但是当多个线程尝试同时更新数据时,第一个线程将获胜,而所有其他线程将因 OptimisticLockException 而失败,他们必须再次尝试执行更新。因此,即使在并发使用的情况下,也不会静默丢失任何更新。
数据库中的每条记录都维护一个版本号。当第一个事务从数据库读取记录时,它也会收到版本。修改后,服务器将记录的版本号与数据库中的版本号进行比较,如果未更改,则使用递增的版本号写入记录。
然后,第二笔交易将以相同的记录编号进入(即两个客户端都在更新该记录)。这一次,服务器将识别出数据库中的版本号在第一次事务中已更改,并拒绝更新。
JPA 乐观锁定允许任何人读取和更新实体,但是在提交时会进行版本检查,如果自上次读取实体以来在数据库中更新了版本,则会引发异常。
如何在 JPA 中启用乐观锁定?
要在 JPA 中为实体启用乐观锁定,只需对属性进行批注,如下面的代码示例所示@Version
@Entity
@Table (name="t_flight")
public class Flight {
@ID
@GeneratedValue (strategy=GenerationType.AUTO)
private int id;
@Version
private long version;
...
}
| 只需两行代码即可在 JPA 中启用乐观锁定。 |
请务必注意,只有短、整型、长和时间戳字段才能使用 @Version 属性进行批注。
时间戳是一种不如版本号可靠的乐观锁定方式,但应用程序也可以将其用于其他目的。
@Entity
@Table ( name = "t_flight" )
public class Flight implements Serializable {
...
@Version
public Date getLastUpdate() { ... }
}
引擎盖下
在后台,JPA 将在每次成功提交时递增 Version 属性。
这会导致 SQL 如下所示(请注意,JPA 处理所有内容,其显示仅用于说明目的):
UPDATE Flight SET ..., version = version + 1 WHERE id = ? AND version = readVersion
乐观方法的利弊
-
乐观锁定的优点是没有数据库锁,这可以提供更好的可伸缩性。
-
缺点是用户或应用程序必须刷新并重试失败的更新。
悲观锁定方法
使用悲观锁定时,hibernate 会锁定记录供您独占使用,直到您提交事务。这通常是在数据库级别使用语句实现的。在这种情况下,任何其他尝试更新/访问同一记录的事务都将被阻止,直到第一个事务释放锁。SELECT … FOR UPDATE
此策略以性能为代价提供更好的可预测性,并且扩展性不大。在内部,它的行为类似于所有线程(读取和写入)对单个记录的顺序访问,这就是可扩展性是一个问题的原因。
通常,如果只是在事务级别(可序列化)指定适当的隔离级别,数据库将自动为您处理它。Hibernate为您提供了在新事务开始时获得独占悲观锁的选项。以下锁定模式可用于在休眠会话中获取悲观锁定。
使用支持该语法的数据库根据显式用户请求获取。SELECT … FOR UPDATE
根据明确的用户请求使用 ain Oracle 获取。SELECT … FOR UPDATE NOWAIT
请注意,使用上述 UPGRADE 模式,您希望修改加载的对象,并且在提交事务之前不希望任何其他线程在您在此过程中更改它。
如何在 Spring Data JPA & Hibernate 中启用悲观锁定
interface Flight extends Repository<Flight, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
Flight findOne(Long id);
}
| 从 Spring Data JPA 的 1.6 版开始,CRUD 方法支持@Lock,一旦您提交事务,锁就会自动释放。 |
tx.begin();
Flight w = em.find(Flight.class, 1L, LockModeType.PESSIMISTIC_WRITE);
w.decrementBy(4);
em.flush();
tx.commit();
事务隔离级别和锁定模式有什么区别?
隔离级别会影响您看到的内容。 锁定模式会影响允许您执行的操作。
为什么不在 Java 级别而不是数据库/休眠级别处理并发?
当我们使用支持事务的数据库时,并发管理必须远离Java代码,而应该由数据库事务隔离级别和锁定策略来处理。造成这种情况的主要原因是——
-
它使我们的代码更简单,在数据库级别处理并发更容易。
-
如果在 JVM 级别管理并发性,则应用程序在移动到多个分布式 JVM 时将中断,因此解决方案将永远无法扩展。另一方面,数据库是单点入口,即使多个 JVM 正在调用并行请求,也可以处理并发。
-
即使您只有单个 JVM 设置,与您自己的手工编织同步 Java 代码相比,乐观锁定也可能产生更高的性能。
本文探讨了数据库中丢失更新的问题,包括乐观锁定和悲观锁定两种解决方法。乐观锁定利用版本号避免更新丢失,而悲观锁定则通过锁定记录确保数据一致性。
318

被折叠的 条评论
为什么被折叠?



