Troubleshooting Java Persistence API (JPA) deadlocks and transaction time-outs
If an application encounters deadlocks and locked time-outs from the database, we can adjust the locking or transaction type to resolve the problem. Transactions are critical to maintaining data integrity. They are used to group operations into units of work that act together in an all-or-nothing fashion. There are two types of transactions, optimistic and pessimistic. JPA for WAS uses optimistic transactions by default, however, OpenJPA also supports pessimistic transactions. This page includes the steps to configure these transactions.
Pessimistic transactions prevent all other concurrent transactions from using or modifying the same data by locking the datastore records that they are using. The advantage of a pessimistic transaction is that conflicts between multiple transactions are avoided. However, preventing these conflicts consumes more database resources than optimistic transactions. Because pessimistic transactions lock their records, it is possible that two transactions can wait for the other to release its lock before continuing. This results in a deadlock. The result is that one transaction is forced to unlock its records after a specified timeout interval and an exception is generated.
Optimistic transactions operate with the assumption that transactions do not conflict with each other. Optimistic transactions do not lock datastore records. Therefore, it is possible for two transactions to access and change the persistent information at the same time. Optimistic transactions consume less database resources than pessimistic transactions but they can be less reliable.
These changes are not detected until one transaction attempts to commit its changes. At that time, the transaction realizes that the data has been changed by another transaction and an OptimisticLockException exception displays. Despite this disadvantage, optimistic transactions are typically a better choice for applications because of performance and a low risk of deadlock.
Locking
Servers use objects called locks to maintain transaction and data integrity. Locks are managed automatically by lock management programs. Locking is handled on a per-transaction basis. When you obtain a lock on an entity, the lock indicates that we have the right to use the entity. The lock indicates that no other user can alter the data of the entity until the lock is released. Locking management ensures that no other user can obtain a lock that conflicts with a lock already in place. A lock cannot be used by more than one user. Locking management automatically assigns and releases locks, according the actions of the user.
To manage locks with JPA for WAS, we need to use the LockManager plug-in. The LockManager controls how the JPA application locks the data involved in the transaction. Depending on the setting used, LockManager determines if locking takes place, and if so, optimistic or pessimistic. In addition to LockManager, we need to control the level of isolation that the lock places on the entities and determine when the locking takes place in the transaction.
JPA for WAS contains advanced locking and versioning APIs for more control over database resources and object versions. The implementation works with the appserver container for managed transactions. This allows the use of the declarative transaction demarcation and JTA implementations within the container.
JPA Access Intent in JPA for WAS provides an application improved control over the settings of isolation level and read lock when accessing data. This ability allows the data access criteria to be set based on the entity use and the taskName associated to the current transaction. By refining the scope of access, the deadlock condition is further minimized.
The following steps show you how to enable optimistic or pessimistic locking:
- Enable Optimistic locking by setting the isolation and LockManager levels: Optimistic locking should be used when the chance of contention between updating transactions is low or when we have to read and update transaction across multiple transactions.
- Verify the LockManager is set to the following version: openjpa.LockManager = "version". This is the default setting.
- Verify the isolation level is set to Read_Committed: openjpa.TransactionIsolation = "Read_Committed". This is the default setting.
- Place the @Version annotation column in the entity class definition.
- Set the LockMode API for the EntityManager to guarantee repeatable read on a per entity instance, if needed: EntityManager.setLock(LockMode.READ) or EntityManager.setLock(LockMode.WRITE).
Optimistic locking is now in place with versions enabled to monitor the entities involved in transactions and repeatable read permitted in case of OptimisticLockException exceptions. If we still experience frequent OptimisticLockException exceptions because transactions are rolled back and reinitiated, you should use pessimistic locking.
- Enable pessimistic locking by modifying the LockManager and isolation levels:
- Change the LockManager from version to pessimistic: openjpa.LockManager="pessimistic"
- Set the isolation level to Repeatable_Read: openjpa.TransactionIsolation="Repeatable_Read".
See about this property, see the OpenJPA documentation.
The locking type is now pessimistic. We might experience deadlocks if two transactions access read data, obtain read locks and attempt to update the data concurrently.
- If we encounter frequent deadlocks, we can modify the pessimistic locking as follows:
- Set the isolation level to Serializable: openjpa.TransactionIsolation="Serializable".
- Modify the ReadLockLevel property to "write": openjpa.ReadLockLevel="write".
We can also use query hint with this property: openjpa.FetchPlan.ReadLockLevel="write".
After the modifications are done, transactions acquire an update lock on data and cause other update transactions to serialize. The transaction throughput is less because transactions are serialized, but they execute without deadlock.
Results
See, consult chapter 9 of the JPA Reference guide.
Related tasks
JPA Access Intent
Task overview: Storing and retrieving persistent data with the Java Persistence API (JPA)