В чем разница между оптимистичной (Optimistic) и пессимистичной (Pessimistic) блокировкой в базах данных?

«В чем разница между оптимистичной (Optimistic) и пессимистичной (Pessimistic) блокировкой в базах данных?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Оптимистичная блокировка предполагает, что конфликты при одновременном изменении данных маловероятны. Она проверяет, не изменились ли данные с момента их чтения, только в момент фиксации транзакции (commit). Если данные были изменены другой транзакцией, выбрасывается исключение (например, OptimisticLockException). Реализуется через поля версии (@Version) или метки времени (timestamp).

Пессимистичная блокировка предполагает частые конфликты и предотвращает их, блокируя строку или таблицу на уровне базы данных сразу при чтении для изменения. Это гарантирует эксклюзивный доступ, но снижает параллелизм.

Ключевые различия:

  • Подход: Оптимистичная — «проверить и обновить», пессимистичная — «заблокировать, затем изменить».
  • Блокировка в БД: Оптимистичная не устанавливает блокировок при чтении, пессимистичная — устанавливает (например, SELECT ... FOR UPDATE).
  • Производительность: Оптимистичная обычно обеспечивает лучшую производительность при низкой конкуренции. Пессимистичная может приводить к ожиданиям и deadlock'ам.
  • Использование: Оптимистичная подходит для сценариев с редкими конфликтами (веб-приложения). Пессимистичная — для критичных данных с высокой конкуренцией (финансовые операции).

Пример оптимистичной блокировки (JPA/Hibernate):

@Entity
public class Account {
    @Id
    private Long id;
    private BigDecimal balance;
    @Version
    private Long version; // Автоматически увеличивается при каждом обновлении
}
// При коммите, если version в БД отличается от version в объекте, будет выброшено OptimisticLockException

Пример пессимистичной блокировки (JPA):

// Блокировка строки для эксклюзивного изменения
Account account = entityManager.find(Account.class, accountId, LockModeType.PESSIMISTIC_WRITE);
// Или с использованием запроса
Query query = entityManager.createQuery("SELECT a FROM Account a WHERE a.id = :id");
query.setParameter("id", accountId);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
Account account = (Account) query.getSingleResult();