How to understand and select optimistic locks and pessimistic locks during back-end development?

The author was asked this practical question when participating in a soft opening interview at a bank, so I came here to learn and summarize it.

To get straight to the point, when choosing between pessimistic locks and optimistic locks, you need to consider the specific application scenarios:

  • Pessimistic locking is suitable for scenarios where data modification operations are frequent and concurrency conflicts are frequent.
  • Optimistic locking is suitable for scenarios where data reading operations are frequent and conflicts are rare

The concept of lock

First understand what is the difference between the two locks

Pessimistic lock

Pessimistic locking is a conservative locking strategy that assumes that conflicts will occur throughout the data access process, so the lock is acquired before the data is accessed. Pessimistic locking is mainly used for data modification operations. It ensures that only one thread can access and modify data in a transaction, and other threads must wait for the lock to be released. The characteristic of pessimistic locking is to block access by other threads to ensure data consistency.

Optimistic locking

Optimistic locking is an optimistic locking strategy that assumes that no conflicts will occur throughout the data access process, so the lock will not be acquired immediately. Optimistic locking is mainly implemented based on version number or timestamp. Each data record will have a version number or timestamp. When the data is updated, the current version number or timestamp will be compared with the version number or timestamp obtained before the update. Consistent. If consistent, it can be updated. Otherwise, it means that the data has been modified by other threads and needs to be processed accordingly (such as rollback or retry). The characteristic of optimistic locking is that it does not block access by other threads and ensures data consistency by detecting and handling conflicts.

Reference

For comments on the lock mechanism and the application of these two lock types in DBMS, we can look at a highly praised answer in stackoverflow.

Optimistic Locking is a strategy where you read a record, take note of a version number (other methods to do this involve dates, timestamps or checksums/hashes) and check that the version hasn’t changed before you write the record back. When you write the record back you filter the update on the version to make sure it’s atomic. (i.e. hasn’t been updated between when you check the version and write the record to the disk) and update the version in one hit .

If the record is dirty (i.e. different version to yours) you abort the transaction and the user can re-start it.

This strategy is most applicable to high-volume systems and three-tier architectures where you do not necessarily maintain a connection to the database for your session. In this situation the client cannot actually maintain database locks as the connections are taken from a pool and you may not be using the same connection from one access to the next.

Pessimistic Locking is when you lock the record for your exclusive use until you have finished with it. It has much better integrity than optimistic locking but requires you to be careful with your application design to avoid Deadlocks. To use pessimistic locking you need either a direct connection to the database (as would typically be the case in a two tier client server application) or an externally available transaction ID that can be used independently of the connection.

In the latter case you open the transaction with the TxID and then reconnect using that ID.

The DBMS maintains the locks and allows you to pick the session back up through the TxID. This is how distributed transactions using two-phase commit protocols (such as XA or COM + Transactions) work.

Original source

Translate and refine

1. Optimistic locking is a strategy that records the version number when reading the record (you can also use date, timestamp or checksum/hash methods), and checks whether the version number has changed before writing the record back. When writing records back, updates are filtered by the version number, ensuring atomicity (i.e. nothing has been updated between checking the version number and writing the record to disk), and updating the version number all at once. If the record has changed (i.e. the version number is different from the current one), the transaction is terminated and the user can start over.

2. Optimistic locking is suitable for high-concurrency systems and three-tier architectures, where the connection to the database may not be maintained. In this case, the client cannot maintain the database lock because the connection is obtained from the connection pool, possibly using a different connection between different accesses.

3. Pessimistic locks lock records exclusively during use and have better integrity than optimistic locks, but care needs to be taken in application design to avoid deadlocks. To use pessimistic locking, either a direct connection to the database is required (as is typically the case in two-tier client-server applications), or there is an externally accessible transaction ID that can be used independently of the connection. In the latter case, you can open the transaction using the TxID and then reconnect using that ID. The database management system maintains the lock and allows session recovery via TxID. This is how distributed transactions using a two-phase commit protocol (such as XA or COM+ transactions) work.

Applications under development

Java

Optimistic locking

Optimistic locking in Java can be implemented by using version numbers or timestamps. Common implementation methods include:

  • Version number implementation: Add a version number field to the data record, add 1 to the version number each time it is updated, and compare whether the version numbers are consistent during the update.
  • Timestamp implementation: add a timestamp field in the data record, update the timestamp every time it is updated, and compare the timestamps to see if they are consistent when updating.

Pessimistic lock

Pessimistic locking in Java can be implemented using the synchronized keyword or the implementation class of the Lock interface (such as ReentrantLock). These lock mechanisms acquire the lock before accessing the data and release the lock after the operation is completed, ensuring that only one thread can access the data at the same time.

Code practice

The following uses the popular article ranking list as the technical background to give the implementation code and analysis of Java’s optimistic lock and pessimistic lock.

Optimistic lock implementation
public class Article {
    private int id;
    private String title;
    private int likes;
    private int version;

    // getters and setters

    public void like() {
        // Get the current version number
        int currentVersion = getVersion();

        //Simulate concurrency conflicts, assuming that other threads update the article
        updateArticleFromDatabase();

        // Compare the version number before update with the current version number
        if (currentVersion == getVersion()) {
            // The version numbers are consistent and can be updated to update the number of likes
            setLikes(getLikes() + 1);
            setVersion(getVersion() + 1);
            saveArticleToDatabase();
        } else {
            //The version numbers are inconsistent, indicating that the data has been modified by other threads and needs to be processed accordingly.
            //You can choose strategies such as rollback or retry
        }
    }

    private void updateArticleFromDatabase() {
        // Get the latest article information from the database and update the properties of the current object
    }

    private void saveArticleToDatabase() {
        //Save the updated article information to the database
    }
}

Each article object has a version number attribute (version). During the like operation, the current version number is first obtained, and then a concurrency conflict is simulated. It is assumed that other threads have updated the article. Then, compare again whether the obtained version number is consistent with the current version number. If they are consistent, perform a like operation and update the version number, and finally save the updated article information to the database. If the version numbers are inconsistent, it means that the data has been modified by other threads, and you can choose strategies such as rollback or retry.

Pessimistic lock implementation
public class Article {
    private int id;
    private String title;
    private int likes;

    // getters and setters

    public synchronized void like() {
        // Like the article
        setLikes(getLikes() + 1);
        saveArticleToDatabase();
    }

    private void saveArticleToDatabase() {
        //Save the updated article information to the database
    }
}

By using the synchronized keyword to modify the like method, ensure that only one thread can access and modify the number of likes of the article at the same time. Other threads accessing this method will be blocked until the lock is released.

MySQL

Optimistic locking

Optimistic locking is usually implemented in MySQL using row versioning (Row Versioning) or timestamp. MySQL provides multiple mechanisms to implement optimistic locking, such as MVCC (Multi-Version Concurrency Control) and CAS (Compare and Set). Optimism is that it will work even at weaker isolation levels (read committed) or when read and write operations continue in subsequent database transactions. But we cannot ignore its shortcomings. If the database access framework catches an optimistic lock exception (OptimisticLockException), a rollback operation will be triggered, so all work done before the current transaction execution will be lost.

Optimistic locking is suitable for the following scenarios:

  • When the data needs to be updated, the version number or timestamp can be used in the update statement. If the version number or timestamp before and after the update is consistent, it means that the data has not been modified by other transactions and the update operation can be performed.
  • When data needs to be read, optimistic locks will not block the modification operations of other transactions, so you can use optimistic locks when reading data to determine whether the data has been modified by comparing the version number or timestamp.

Pessimistic lock

Pessimistic locking is usually implemented in MySQL using the SELECT… FOR UPDATE statement. When a SELECT… FOR UPDATE statement is executed, MySQL will add locks to the selected rows to ensure that other transactions cannot modify or read these rows, thus achieving the effect of pessimistic locking. Pessimistic locking is suitable for the following scenarios:

  • When you need to update data, you can use the SELECT… FOR UPDATE statement in the transaction to obtain a pessimistic lock to ensure that other transactions cannot modify the data before the transaction is committed.
  • When you need to read data, if you have high data consistency requirements, you can also use the SELECT … FOR UPDATE statement to obtain a pessimistic lock and block other transactions from modifying the data.

Note that when we use MySQL, there are many locks to choose from, such as table locks, row-level locks, gap locks, etc. You can choose the appropriate lock mechanism to achieve concurrency control according to specific needs.

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 139,496 people are learning the system