← Назад к вопросам

В чем разница между Optimistic lock и Pessimistic lock?

2.8 Senior🔥 131 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Optimistic Lock vs Pessimistic Lock

Это два противоположных подхода к управлению конкурентностью в базе данных. Выбор между ними критично влияет на производительность и надёжность системы. За 10+ лет я видел много проектов, которых мучили проблемы из-за неправильного выбора.

Pessimistic Lock (Пессимистическая блокировка)

Пессимистический подход: предположим, что конфликты БУДУТ, поэтому заблокируем ресурс сразу.

// Pessimistic Lock: SELECT FOR UPDATE
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    // Блокируем ДО начала операции
    Account from = accountRepository.findByIdWithLock(fromId);  // SELECT ... FOR UPDATE
    Account to = accountRepository.findByIdWithLock(toId);      // SELECT ... FOR UPDATE
    
    from.withdraw(amount);
    to.deposit(amount);
    
    accountRepository.save(from);
    accountRepository.save(to);
    // Блокировка снимается при COMMIT
}

@Query("SELECT a FROM Account a WHERE a.id = :id")
@Lock(LockModeType.PESSIMISTIC_WRITE)
Account findByIdWithLock(@Param("id") Long id);

Как это работает:

Транзакция 1              Транзакция 2
---------                 ---------
BEGIN;
                          BEGIN;
SELECT * FROM account
WHERE id = 1 FOR UPDATE;  -- Блокирует строку
(блокировка захвачена)    
                          SELECT * FROM account
                          WHERE id = 1 FOR UPDATE;
                          -- ЖДЁТ! Блокировка занята
UPDATE account SET ...
COMMIT;                   -- Блокировка освобождается
                          -- Теперь может продолжить
                          (получает блокировку)
                          UPDATE account SET ...
                          COMMIT;

Характеристики:

  • Предполагает конфликты
  • Блокирует ресурс сразу
  • Потокобезопасно по умолчанию
  • Может привести к deadlock'ам
  • Меньше retry'ев

Optimistic Lock (Оптимистическая блокировка)

Оптимистический подход: предположим, что конфликтов НЕ будет, работаем параллельно, но проверяем при сохранении.

// Optimistic Lock: используем версию
@Entity
@Table(name = "accounts")
public class Account {
    @Id
    private Long id;
    
    private BigDecimal balance;
    
    @Version  // Версия для optimistic lock
    private Long version;
}

// В БД:
CREATE TABLE accounts (
    id BIGINT PRIMARY KEY,
    balance DECIMAL,
    version BIGINT DEFAULT 0
);

// Использование:
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    Account from = accountRepository.findById(fromId).get();  // version = 10
    Account to = accountRepository.findById(toId).get();      // version = 5
    
    // Другой поток может изменить from параллельно!
    
    from.withdraw(amount);
    to.deposit(amount);
    
    // При сохранении проверяется версия
    accountRepository.save(from);
    // JPA выполнит:
    // UPDATE account SET balance = ?, version = ? WHERE id = ? AND version = ?
    // Если версия не совпадает -> OptimisticLockException!
}

Как это работает:

Транзакция 1              Транзакция 2
---------                 ---------
BEGIN;
                          BEGIN;
SELECT * FROM account
WHERE id = 1;             -- version = 10
(версия сохранена)        
                          SELECT * FROM account
                          WHERE id = 1;  -- version = 10
                          (версия сохранена)
UPDATE account SET balance = 500
WHERE id = 1 AND version = 10;  -- Успех!
UPDATE version = 11;
COMMIT;
                          -- Теперь версия = 11
                          UPDATE account SET balance = 600
                          WHERE id = 1 AND version = 10;  -- FAIL!
                          -- Версия уже 11, а не 10
                          -- OptimisticLockException!
                          ROLLBACK;

Характеристики:

  • Предполагает отсутствие конфликтов
  • Не блокирует ресурс
  • Проверка при сохранении
  • Может быть много retry'ев
  • Лучше для read-heavy сценариев

Сравнительная таблица

Аспект              Pessimistic    Optimistic
-----------------------------------------------
Предположение       Конфликты      Нет конфликтов
Блокировка          Сразу          При сохранении
Блокирующие         Да             Нет
Deadlock            Возможен        Маловероятен
Retry'и             Редко           Частые
Производительность  Низкая при      Высокая при
                    конкурентности  низкой конкурентности
Data consistency     Очень высокая  Высокая
Сложность           Просто          Сложнее

Когда использовать Pessimistic Lock

Используй pessimistic, если:

  • Высокая вероятность конфликтов
  • Финансовые операции
  • Критичные бизнес-операции
  • Несколько процессов изменяют один ресурс
@Transactional
@Lock(LockModeType.PESSIMISTIC_WRITE)
public void criticalOperation(Long id) {
    // Высокая гарантия консистентности
}

Когда использовать Optimistic Lock

Используй optimistic, если:

  • Низкая вероятность конфликтов
  • Read-heavy приложение
  • Высокая конкурентность
  • Мало одновременных изменений одного ресурса
@Entity
public class BlogPost {
    @Version
    private Long version;  // Оптимистическая блокировка
}

Обработка OptimisticLockException

@Transactional
public void transferMoneyWithRetry(Long fromId, Long toId, BigDecimal amount) {
    int attempts = 0;
    while (attempts < 3) {
        try {
            transferMoney(fromId, toId, amount);
            return;
        } catch (OptimisticLockException e) {
            attempts++;
            if (attempts >= 3) {
                throw new TransferFailedException("Max retries exceeded", e);
            }
            // Retry: обновляем состояние из БД
            // Spring автоматически откатит транзакцию
        }
    }
}

Spring Data JPA примеры

Pessimistic Lock

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT a FROM Account a WHERE a.id = :id")
    Optional<Account> findByIdForUpdate(@Param("id") Long id);
}

Optimistic Lock

@Entity
public class Account {
    @Id
    private Long id;
    
    @Version  // Spring автоматически управляет
    private Long version;
    
    private BigDecimal balance;
}

// При сохранении Spring сам будет проверять версию
accountRepository.save(account);  // Может выбросить OptimisticLockException

Гибридный подход

Иногда комбинируют оба подхода:

@Transactional
public void complexOperation(Long id1, Long id2) {
    // Для критичного ресурса используем pessimistic
    Account critical = accountRepository.findByIdForUpdate(id1);
    
    // Для остального используем optimistic (есть @Version)
    BlogPost post = blogRepository.findById(id2).get();
    
    // Обновляем
    critical.update();
    post.update();
    
    accountRepository.save(critical);
    blogRepository.save(post);
}

Мой совет

Начни с optimistic lock (через @Version). Это простейший и часто эффективный подход. Если увидишь слишком много OptimisticLockException'ов в логах, переходи на pessimistic. Но помни: pessimistic может привести к deadlock'ам и снижению throughput'а.

В реальности большинство операций имеют низкую вероятность конфликтов, поэтому optimistic lock часто лучший выбор. Финансовые операции — исключение, там нужна pessimistic с SELECT FOR UPDATE.

В чем разница между Optimistic lock и Pessimistic lock? | PrepBro