Transactions and Locking Demystified: Optimistic vs. Pessimistic Locking for Smooth Database Updates

2024-04-06

Locking: Imagine a database record as a file. Locking means temporarily restricting access to that file while someone is working on it. This prevents others from modifying the same data at the same time, which could lead to inconsistencies.

Transactions: A transaction is a series of database operations that are treated as a single unit. Either all the operations succeed, or none of them do. This ensures data integrity.

Optimistic Locking:

  • Idea: Be optimistic and assume multiple users won't edit the same data simultaneously.
  • Process:
    1. User reads a record's data, along with a version number (e.g., timestamp).
    2. User edits the data.
    3. User tries to save the changes.
    4. Database checks if the version number in the record matches the one the user has.
      • If they match, the update succeeds (data wasn't modified by another user since the read).
      • If they don't match, it means another user updated the data in between. The update fails, and the user might be shown a message to refresh and retry.
  • Benefits:
    • Faster performance, especially in low contention scenarios (when edits are infrequent).
    • No waiting for locks.
  • Drawbacks:
    • Lost updates can occur if multiple users edit the same record concurrently. One update will overwrite the other.
  • Idea: Be pessimistic and assume conflicts might happen. Acquire exclusive access to the data upfront to prevent conflicts.
  • Process:
    1. User tries to edit a record.
    2. Database locks the record, preventing others from accessing it.
    3. User edits the data.
    4. User saves the changes, and the lock is released.
  • Benefits:
    • Guaranteed data consistency - no lost updates.
  • Drawbacks:
    • Slower performance, especially in high contention scenarios (many users editing frequently).
    • Can lead to deadlocks (situations where two users are waiting for each other's locks to be released).

Choosing the right approach:

  • Use optimistic locking for scenarios with low contention (edits are infrequent) and good user experience is crucial (faster updates).
  • Use pessimistic locking for scenarios with high contention (frequent edits) or data consistency is paramount (financial transactions).



Optimistic Locking (Python with SQLAlchemy):

from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# Database setup
engine = create_engine('sqlite:///data.db')
Base = declarative_base()

class Product(Base):
  __tablename__ = 'products'
  id = Column(Integer, primary_key=True)
  name = Column(String)
  price = Column(Integer)
  version = Column(DateTime, nullable=False)

# Session 
Session = sessionmaker(bind=engine)
session = Session()

# Fetch product for update (with version number)
product = session.query(Product).filter_by(id=1).first()

# Modify product data
product.price = product.price + 10

# Try to update (with version check)
try:
  session.add(product)
  session.commit()
  print("Product updated successfully!")
except:
  print("Update failed - Another user might have modified the product.")
  session.rollback()

session.close()

This example uses a version column with a timestamp to track data changes. During update, it checks if the version in the database matches the one fetched earlier. If they differ, an update conflict is assumed.

Pessimistic Locking (Java with JPA):

import javax.persistence.*;

@Entity
public class Product {
  @Id
  private int id;
  private String name;
  private double price;

  // Additional getters and setters

  @Version
  private Long version;
}

// ... (rest of your code)

EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU");
EntityManager em = emf.createEntityManager();

try {
  // Begin transaction with locking
  em.getTransaction().begin();
  Product product = em.find(Product.class, 1, LockModeType.PESSIMISTIC_WRITE);

  // Modify product data
  product.setPrice(product.getPrice() * 0.9);

  // Update with guaranteed consistency (due to lock)
  em.merge(product);
  em.getTransaction().commit();
  System.out.println("Product updated successfully!");
} catch (Exception e) {
  e.printStackTrace();
  em.getTransaction().rollback();
} finally {
  em.close();
  emf.close();
}

Here, @Version annotation is used for optimistic locking (similar to the previous example). But, we explicitly acquire a pessimistic lock using LockModeType.PESSIMISTIC_WRITE during the transaction. This ensures exclusive access to the data until the transaction commits.




  1. Timestamp-based Locking: This is a variation of optimistic locking with finer-grained control. Instead of a single version number for the entire record, timestamps are associated with specific data fields. During update, the database checks if the timestamps for modified fields match the ones fetched earlier. This allows concurrent edits to unchanged fields while maintaining consistency for modified ones.

  2. Read Committed and Serializable Isolation Levels: These are database transaction isolation levels. Read committed ensures you see a consistent view of data as of the moment you read it, but allows "dirty reads" (seeing uncommitted changes from other transactions). Serializable guarantees complete isolation, preventing even "dirty reads," but can lead to performance overhead. The choice depends on your specific needs.

  3. Retry Logic: In optimistic locking scenarios, instead of simply failing on conflicts, you can implement retry logic. This could involve prompting the user to refresh and retry their update, with a backoff mechanism to avoid overwhelming the database with retries.

  4. Database-specific Features: Many databases offer additional concurrency control mechanisms. For example, Oracle has "MVCC" (Multiversion Concurrency Control) that maintains historical versions of data, allowing reads to access past versions without affecting ongoing updates.

  5. Locking at the Application Layer: In some cases, you might implement locking at the application layer using mechanisms like distributed locks or message queues. This can be useful for coordinating access across multiple database servers or services.

Choosing the best alternative depends on your specific use case, data access patterns, and desired balance between performance and data consistency. Consider factors like:

  • Frequency of edits: If edits are infrequent, optimistic locking might suffice. For high contention scenarios, pessimistic locking or alternatives like timestamps might be better.
  • Data consistency requirements: If absolute consistency is crucial (e.g., financial transactions), pessimistic locking or serializable isolation is preferred.
  • Performance needs: For low-latency updates, optimistic locking or timestamp-based approaches might be faster than pessimistic locking.

database transactions locking


Language-Agnostic Secrets: Crafting Clear and Consistent Database Names

What are Naming Conventions?Imagine a giant library with bookshelves overflowing with books. To find a specific book quickly...


Enforcing Data Integrity with Foreign Keys in SQL Server CE

Example: Imagine a database for an online store. You have two tables:Customers (CustomerID, CustomerName, Address)Orders (OrderID...


Keep Your Database Organized: Best Practices for Documentation

Using comments within SQL code:This approach embeds comments directly within your SQL code, alongside the structure definition (DDL) statements...


Troubleshooting the "mysql_connect() No such file or directory" Error in PHP

Understanding the Error:PHP: This error originates from your PHP code, specifically the mysql_connect() function, which is used to establish a connection with a MySQL database server...


database transactions locking