В чем разница между Optimistic lock и Pessimistic lock?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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.