The previous post discussed the “Optimistic Logic” and how Hibernate implements it.
This new post will discuss the “Pessimistic Logic” technique and how to implement it using Hibernate.
Table of Contents
What is the Pessimistic Locking?
Locking is a critical concept in the world of databases and transaction management. As the name suggests, Pessimistic Locking takes a conservative approach to handling concurrent data access. Here, we’ll delve deep into Pessimistic Locking, comparing it to its counterpart, Optimistic Locking.
In a Pessimistic Locking approach, a record (or row) in the database is locked as soon as someone starts modifying it, preventing others from accessing the same record for updates until it is released. This strategy is called ‘pessimistic’ because it assumes the worst—that conflicts will arise—and takes steps to prevent them.
Pessimistic Locking in raw SQL
The way to achieve Pessimistic Locking in SQL can differ based on the RDBMS used. Let’s delve into both MySQL and PostgreSQL for a clearer understanding.
Pessimistic Write Lock
A Pessimistic Write Lock is used when a transaction intends to update or delete data and wants to ensure that no other transactions can read, update, or delete the same data until it’s done. This lock type is crucial for maintaining data integrity during critical update operations.
MySQL
START TRANSACTION; SELECT * FROM table_name WHERE condition FOR UPDATE; -- Update or delete operations here COMMIT;
In MySQL, a Pessimistic Write Lock is acquired using the FOR UPDATE
clause in a SELECT
statement. When a row is locked with FOR UPDATE
, other transactions can neither modify nor acquire a write lock on the same row until the lock is released. However, they can still read the data unless they attempt to acquire a write lock or use a locking read themselves.
PostgreSQL
BEGIN; SELECT * FROM table_name WHERE condition FOR UPDATE; -- Update or delete operations here COMMIT;
PostgreSQL also uses the FOR UPDATE
clause to acquire a Pessimistic Write Lock. Similar to MySQL, this lock prevents other transactions from acquiring read locks (with FOR SHARE
) or write locks (with FOR UPDATE
) on the locked rows.
Pessimistic Read Lock
A Pessimistic Read Lock is used when a transaction needs to read data and ensures that other transactions cannot modify the data until it is complete. However, other transactions can still read the locked data, unlike write locks.
MySQL
START TRANSACTION; SELECT * FROM table_name WHERE condition LOCK IN SHARE MODE; -- Further read operations COMMIT;
In MySQL, the LOCK IN SHARE MODE
clause is used to implement a Pessimistic Read Lock. This allows multiple transactions to read the same data without interfering with each other while preventing any of them from modifying the data.
PostgreSQL
BEGIN; SELECT * FROM table_name WHERE condition FOR SHARE; -- Further read operations COMMIT;
PostgreSQL uses the FOR SHARE
clause for acquiring a Pessimistic Read Lock. This lock allows other transactions to read the locked rows but prevents them from obtaining a write lock on these rows.
Key Differences
- Purpose: Write locks are more restrictive, ensuring exclusive access for update/delete operations, whereas read locks are designed for scenarios where you want to ensure the data being read does not change during the transaction.
- Concurrency: Write locks significantly reduce concurrency by preventing other transactions from accessing the locked data for writes and, depending on the database and the specific type of read lock used, possibly for reads. Read locks allow higher concurrency by permitting other transactions to read the locked data.
- Usage Scenario: Use write locks when performing operations that should not be interrupted or invalidated by other concurrent operations. Read locks are helpful when you must perform complex calculations or reports on data that must not change during the operation.
Choosing between these locks (or combining them) depends on your specific application requirements, including the level of data integrity and concurrency needed. It’s also important to consider the performance implications of locking strategies, as excessive Locking can lead to bottlenecks and reduced application throughput.
Concurrency Effects of Pessimistic Locking Strategies
Creating a table illustrating what actions other transactions can perform when a transaction has acquired a Pessimistic Read or Write lock provides a clear, comparative view of the concurrency behaviours permitted by these locking strategies. This understanding is crucial for designing applications that require transactional integrity without sacrificing performance. The table below generalizes the actions based on typical SQL database behaviours, such as those found in PostgreSQL and MySQL:
Action/ Lock Type | Pessimistic Read Lock (FOR SHARE / LOCK IN SHARE MODE ) | Pessimistic Write Lock (FOR UPDATE ) |
---|---|---|
Read without Lock | Allowed | Allowed |
Read with Read Lock | Allowed | Blocked until the lock is released |
Read with Write Lock | Blocked until the lock is released | Blocked until the lock is released |
Write (Update/Delete) | Blocked until the lock is released | Blocked until lock is released |
Notes:
- Read without Lock: Refers to transactions that perform a read operation without attempting to acquire any lock on the data. These operations are generally allowed under both read and write pessimistic locks, as they don’t interfere with the integrity constraints that the locks aim to enforce. However, the exact behaviour might depend on the transaction’s isolation level.
- Read with Read Lock: Indicates a transaction attempting to read data and acquire a read lock (e.g., FOR SHARE in PostgreSQL). This is typically allowed under a Pessimistic Read Lock since read locks are designed to be shared, promoting read concurrency. However, when a write lock is held by another transaction, acquiring a read lock is not allowed until the write lock is released to prevent phantom reads in certain isolation levels.
- Read with Write Lock: Refers to transactions that attempt to read data and acquire a write lock on it (e.g.,
FOR UPDATE
). This operation is blocked by both read and write locks held by other transactions to ensure data integrity until the lock is released. - Write (Update/Delete): Transactions attempting to write to data (update or delete operations) are blocked in both scenarios until the existing lock (read or write) is released. This ensures that data modifications do not violate the integrity constraints enforced by either type of lock.
Key Takeaways:
- Concurrency: Pessimistic Read Locks allow multiple transactions to read concurrently but protect against modifications. Pessimistic Write Locks offer the highest level of data protection by preventing other transactions from performing any write operations or acquiring Write Locks on the locked data.
- Deadlock Potential: While these locking strategies enforce data integrity, they also increase the potential for deadlocks, especially with write locks. Applications should implement deadlock detection and resolution strategies.
- Performance Considerations: Excessive use of locks, mainly write locks, can significantly impact application performance by reducing concurrency. It’s essential to use these locks judiciously and keep the transaction scope as small as possible to optimize performance.
This table offers a generalized view, and the exact behaviour can vary based on the database system, configuration, and transaction isolation levels. Always refer to your specific database’s documentation for precise behaviour.
Pessimistic Locking in Hibernate
Hibernate offers various LockMode
options to handle different levels of data locking, which can be crucial for maintaining data integrity in your application. Understanding each lock mode and its use case is essential for practical Hibernate usage. Let’s explore these modes with more clarity and provide examples where applicable.
LockMode.WRITE
- Description: This lock mode is automatically acquired by Hibernate when it updates or inserts a row in the database. It’s more of an internal mechanism than something you’d manually set in your code.
- Example Usage: There’s no need to use it explicitly
LockMode.WRITE
in your code, as Hibernate handles this automatically when performing insert or update operations.
LockMode.UPGRADE
- Description: This mode is used to explicitly acquire a Pessimistic Write Lock on a database row, equivalent to a
SELECT ... FOR UPDATE
SQL statement. It’s useful when you want to lock a row for updates and ensure no other transactions can be updated or deleted until your current transaction is completed. - Example Usage:
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Book book = session.get(Book.class, 1, LockMode.UPGRADE); // Modifications to the book entity tx.commit(); session.close();
LockMode.UPGRADE_NOWAIT
- Description: Similar to
UPGRADE
, but it tells the database to throw an error immediately if the requested lock cannot be obtained rather than waiting for the lock to become available. This mode prevents your application from hanging in scenarios where lock contention is high. Note that this mode is specific to databases that support theNOWAIT
directive, such as Oracle. - Example Usage: (Assuming the database supports
NOWAIT
)
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); try { Book book = session.get(Book.class, 1, LockMode.UPGRADE_NOWAIT); // Modifications to the book entity } catch (PessimisticLockException e) { // Handle lock acquisition failure } tx.commit(); session.close();
LockMode.READ
- Description: This lock mode is acquired automatically by Hibernate when reading data under the
Repeatable Read
orSerializable
isolation levels to ensure that the read data remains consistent throughout the transaction. The user can explicitly request it. - Example Usage: Explicit usage might look like this, though relying on Hibernate’s automatic handling under the appropriate isolation levels is more common.
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Book book = session.get(Book.class, 1, LockMode.READ); // Read operations on the book entity tx.commit(); session.close();
LockMode.NONE
- Description: Indicates the absence of a lock. All entities are switched to this lock mode at the end of a transaction. Additionally, entities associated with the session via
update()
orsaveOrUpdate()
start in this lock mode. - Example Usage: Generally, you don’t need to set it explicitly with
LockMode.NONE
, as it’s the default state. However, if you need to downgrade a lock for some reason, you can do so:
session.lock(book, LockMode.NONE);
Hibernate LockMode and SQL Comparison
Understanding and correctly applying Hibernate’s LockMode
options can significantly influence your application’s consistency, performance, and reliability. While LockMode.WRITE
and LockMode.READ
are generally managed automatically based on your transaction and isolation settings, LockMode.UPGRADE
and LockMode.UPGRADE_NOWAIT
can be explicitly used to manage locking behaviour for specific entities, offering a finer degree of control over concurrent data access.
Creating a table to compare Hibernate lock modes with native SQL’s locking clauses (like FOR UPDATE
and FOR SHARE
) offers a clear understanding of the correspondence between high-level ORM behaviour and underlying database operations. This comparison assumes a general understanding that database-specific SQL syntax might vary slightly, but the concepts remain broadly applicable across RDBMS like PostgreSQL, MySQL, Oracle, etc.
Hibernate Lock Mode | SQL Equivalent | Description |
---|---|---|
LockMode.NONE | No lock clause | Applies a shared lock, allowing other transactions to read the locked row but preventing them from acquiring a write lock. |
LockMode.READ | FOR SHARE (PostgreSQL) LOCK IN SHARE MODE (MySQL) | Similar to UPGRADE , but if the lock cannot be immediately acquired, it causes the transaction to fail rather than wait. Databases like Oracle and PostgreSQL 9.1+ support this behaviour. |
LockMode.UPGRADE | FOR UPDATE | Acquires an exclusive lock on the row, preventing other transactions from reading (with locking) or writing to the locked row until the current transaction completes. |
LockMode.UPGRADE_NOWAIT | FOR UPDATE NOWAIT (where supported) | It is not directly mapped to a specific SQL clause but represents Hibernate’s internal mechanism to lock rows it’s updating or inserting. |
LockMode.WRITE | Automatically managed by Hibernate during INSERT or UPDATE operations | Similar to UPGRADE , but if the lock cannot be immediately acquired, it causes the transaction to fail rather than wait. This behaviour is supported by databases like Oracle and PostgreSQL 9.1+. |
Notes:
- Database Support: The exact SQL syntax and support for locking clauses
FOR UPDATE NOWAIT
can vary between different databases. Always refer to your specific database’s documentation for precise syntax and behaviour. - Hibernate’s Abstraction: Hibernate abstracts these details, allowing developers to work with a consistent API across different databases. However, understanding the underlying SQL can help optimize performance and avoid lock contention.
- Locking Behavior: The behaviour and impact of these locks on transaction concurrency, isolation, and performance can significantly differ. For example, using
FOR UPDATE
locks too liberally can lead to deadlocks or reduced throughput due to excessive Locking.
This table serves as a guideline to understand how Hibernate’s LockMode
options translate to SQL locking mechanisms, bridging object-relational mapping concepts and database-level locking strategies.
Advantages and Disadvantages of Pessimistic Locking
Advantages:
- Certainty: Minimizes risk of update conflicts.
- Simplicity: It can be simpler in high contention scenarios than handling optimistic lock exceptions.
Disadvantages:
- Potential Deadlocks: Multiple transactions trying to lock can lead to deadlocks.
- Reduced Concurrency: Limits the number of concurrent users/transactions.
- Resource Intensive: Long-held locks can impact performance and exhaust system resources.
Pessimistic vs. Optimistic Locking
Optimistic Locking doesn’t lock records immediately. Instead, it checks for conflicts when attempting to commit. A conflict arises if another transaction has updated the same data, typically resulting in an exception.
Key differences:
- Concurrency: Optimistic Locking allows better concurrency but may lead to more conflicts.
- Performance: Pessimistic Locking can impact system performance in high-contention scenarios, while optimistic Locking offers better scalability.
- Complexity: Optimistic Locking may require handling conflicts and retries, adding to the code complexity.
Conclusion
Choosing between Pessimistic and Optimistic Locking requires carefully evaluating your specific use case, system requirements, and potential for data contention. Pessimistic Locking offers a straightforward approach in scenarios with known contention, while optimistic Locking shines in highly concurrent systems due to its scalability. Proper understanding and testing under realistic conditions will guide you to the right choice for your application.
Thanks for reading. Stay tuned!
Learn. Grow. Teach.