The difference between pessimistic locking and optimistic locking in various entity lock modes of JPA

In order to be able to access entities synchronously, JPA provides two locking mechanisms. Both of these mechanisms prevent one of the two transactions from overwriting the other's data without knowing it.

With entity locking, we generally want to avoid the following situations in two concurrent transactions:

  1. Adam's transaction reads data X
  2. Barbara's transaction reads data X
  3. Adam's transaction modifies data X, and modifies it to XA
  4. Adam's transaction writes data XA
  5. Barbara's transaction modifies data X and modifies it to XB
  6. Barbara's transaction writes data XB

As a result, the changes Adam made were completely overwritten by Barbara, but Barbara had no idea about it. Situations like this are often referred to as " dirty reads ". Obviously, we want Adam to write to XA, and Barbara needs to check for modifications to XA before writing to XB.

How optimistic locking works

Optimistic locking is based on the assumption that in practice conflicts rarely occur, and even if they do, throwing an error is more acceptable and simpler than trying to avoid them. In optimistic locking, one transaction is allowed to complete correctly, but another transaction needs to throw an exception and roll back, and must be re-executed or discarded.

Let's also take Adam and Barbara as examples, here is a possible situation using optimistic locking:

  1. Adam's transaction reads data X
  2. Barbara's transaction reads data X
  3. Adam's transaction modifies data X, and modifies it to XA
  4. Adam's transaction writes data XA
  5. Barbara's transaction modifies data X and modifies it to XB
  6. Barbara's transaction attempted to write data XB, but received an error
  7. Barbara needs to read data XA (or start a new transaction again)
  8. Barbara's transaction modifies data XA and modifies it to XAB
  9. Barbara's transaction writes data to XAB

As you can see, Barbara is forced to check Adam's changes, and she can choose to continue editing Adam's results and save (merge changes). The final data will include both Adam and Barbara's modifications.

Optimistic locking is completely controlled by JPA. It requires an additional version number column to be stored in the DB table. It completely relies on the underlying DB engine to store relational data to work.

How Pessimistic Locking Works

For some people, pessimistic locking is more acceptable. When a transaction needs to modify an entity that may be modified by other transactions at the same time, the transaction issues a command to lock the entity. All locks are automatically released until the end of the transaction.

A situation with pessimistic locking might look like this:

  1. Adam's transaction reads data X
  2. Adam's transaction locks X
  3. Barbara's transaction wants to read data X, but because X is locked, it has to wait
  4. Adam's transaction modifies data X, and modifies it to XA
  5. Adam's transaction writes data XA
  6. Barbara's transaction reads data XA
  7. Barbara's transaction modifies data XA and modifies it to XAB
  8. Barbara's transaction writes data to XAB

As you can see, Barbara is again forced into XAB, which also includes Adam's changes. However, this scheme is completely different from optimistic locking - Barbara needs to wait for Adam's transaction to complete before reading the data. What's more, for this scenario to work correctly, we need to manually issue a lock command in both transactions . (Because we are not sure which transaction runs first, both transactions need to be locked before modifying the data) Although optimistic locking adds a version column to each entity, slightly more work than pessimistic locking, but then we do not need The lock operation is then initiated in the transaction. JPA does all the checks automatically, we just need to handle possible exceptions.

Pessimistic locking uses the locking mechanism provided by the underlying database to lock existing records in the table. JPA needs to know how to trigger these locks, and some databases aren't fully supported yet.

Even the JPA spec says that there is no need to provide PESSIMISTIC_READ (because many DBs only support WRITE locks):

A JPA implementation is allowed to be LockModeType.PESSIMISTIC_WRITEused instead LockModeType.PESSIMISTIC_READ, but not the other way around.

Lock types available in JPA

First of all, I would like to say that for a column annotated with @Version in an entity, JPA will automatically use optimistic locking for that entity. You don't need to use any lock command. However, you can issue a lock of the following types at any time:

  1. LockModeType.Optimistic
    1. 这就是默认的锁类型。也是如ObjectDB所说通常被大家所忽略的锁类型。在我的印象中,只有在需要动态获取并传递锁类型时,才会用到它,即使我们很清楚最后的锁是OPTIMISTIC的。虽然这个例子不太恰当,但是一个好的API设计,即使是默认值也应该为其提供一个可选项。
    2. 示例:Java
LockModeType lockMode = resolveLockMode();
A a = em.find(A.class, 1, lockMode);
  1. LockModeType.OPTIMISTIC_FORCE_INCREMENT

    1. 这个选项很少被用到。但是如果你希望用另一个实体来锁住对当前实体的引用,就需要使用它。换句话说,即使当前实体没有被修改,但是其他实体可能因为当前实体被修改,你就可以用它来锁住对当前实体的引用。
    2. 示例:
      1. 假设我们有两个实体“书(Book)”和“书架(Shelf)”。我们可以将书添加到书架中,但是书不持有对其书架的引用。我们需要对所有移动书到其他书架的动作加锁,以避免一本书被放在2个书架上。为了锁住这个动作,光锁住当前的书架实体是不够的,因为书可能还没有放到某个书架上。锁住所有书架也不合理,因为他们在不同的事务中可能都是不同的。唯一合理的是锁住书实体本身,即使在我们这个例子中它并没有发生变化(因为它并不持有其书架的引用)。
  2. LockModeType.PESSIMISTIC_READ

    1. 这个模式类似于LockModeType.PESSIMISTIC_WRITE,但是有一点不同:如果没有事务对实体加写锁,那么就不能阻塞对该实体的读取。它还允许其他事务使用LockModeType.PESSIMISTIC_READ来加锁。WRITE锁和READ锁之间的区别,已经被这两篇文章(here (ObjectDB) 和 here (OpenJPA))很详细的说明了。但是,不仅因为规范中允许,而且许多实现也没有分开处理,所以该锁模式经常被等价于LockModeType.PESSIMISTIC_WRITE
  3. LockModeType.PESSIMISTIC_WRITE

    1. 这是LockModeType.PESSIMISTIC_READ的增强版。当WRITE锁发生时,JPA在数据库的帮助下,会阻止其他事务读取实体,而不像READ锁那样只禁止写入。
  4. LockModeType.PESSIMISTIC_FORCE_INCREMENT

    1. 这是另一个很少使用的锁模式。但是,它可以用来结合PESSIMISTICOPTIMISTIC时使用。在以下场景中,单独使用PESSIMISTIC_WRITE是无效的:

      1. 事务A使用乐观锁并读取实体E
      2. 事务B请求实体E上的WRITE锁
      3. 事务B提交并释放E上的锁
      4. 事务A更新E并提交
    2. 在步骤4中,如果事务B没有增加版本列的值,那么就无法阻止事务A覆盖B的修改。即使事务B使用的是悲观锁,锁模式LockModeType.PESSIMISTIC_FORCE_INCREMENT也会强制事务B更新版本号,并让事务A失败并抛出OptimisticLockException

为了发起一个指定类型的锁,JPA提供了以下方法:

你可以使用JPA中这两种锁机制中的任意一种。如果需要,也可以选择悲观锁类型PESSIMISTIC_FORCE_INCREMENT,把二者混起来用。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326523227&siteId=291194637