Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Hibernate do row version check for Optimistic Locking before committing the transaction

When before committing the current transaction hibernate checks the version of the row, it should issue an sql select statement for fetching ithe row.

Assume that after issuing that select statement hibernate finds out that the row version is not changed, hence it should proceed with committing the transaction.

I'm wondering how hibernate can be sure that in time slot between selecting the row and committing the current transaction no any other transaction will update the row changing its version number ? The only possible thing that hibernate can do seems to be the row version selection with pessimistic locking using Select ... For Update or a transaction with such an isolation level which will lock the row that is being read.

If what I am thinking is true:

  • then hibernate optimistic locking does actually use a pessimistic locking for its operation although that pessimistic lock is held for a very short time as the transaction will be committed immediately after that.

  • otherwise we have a short time slot between row version check and a commit, where a race condition can occur.

Please share your thoughts.

like image 879
Suren Aznauryan Avatar asked Jan 27 '23 06:01

Suren Aznauryan


1 Answers

For the default optimistic locking mechanism, the one given by the @Version annotation, there is no such risk.

Optimistic locking does not require any extra SELECT to get and check the version after the entity was modified. So, there are two steps involved:

  1. The entity is fetched from the DB along with its version:

     SELECT * FROM PRODUCT WHERE ID = 1;
    
  2. The UPDATE or DELETE will use the version fetched by the same SELECT that fetched the entity:

     UPDATE PRODUCT SET (LIKES, QUANTITY, VERSION) = (5, 10, 3) 
     WHERE ID = 1 AND VERSION = 2;
    

So, Hibernate does not check the entity version. The DB checks it using the WHERE clause.

Hibernate only checks the updateCount result of the PreparedStatement.executeUpdate method call. If the count is not updateCount, it means that either the row was deleted or the version has changed, meaning that we are using stale data so an OptimisticLockException will be thrown.

Therefore, no conflicts can occur for the default @Version based optimistic locking because a record can only be modified by a single transaction at a time, and once the row is locked by a modification, the lock will be kept until the transaction commits or rolls back.

Only the explicit LockModeType.OPTIMISTIC can lead to race conditions. However, you can easily fix that using a pessimistic shared or explicit lock.

like image 133
Vlad Mihalcea Avatar answered Feb 07 '23 11:02

Vlad Mihalcea