Когда выгоднее использовать Pessimistic lock?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Pessimistic Lock: когда его использовать
Pessimistic lock — это стратегия управления конкурентностью, где система захватывает блокировку на ресурс ДО его изменения, предполагая, что конфликты будут часто случаться. Это противоположность оптимистичному подходу.
Основное различие между стратегиями
Optimistic Lock (оптимистичный):
- Предполагаем: конфликты РЕДКИЕ
- Берём версию данных
- Изменяем без блокировки
- Проверяем версию при сохранении
- Если версия изменилась → откатываем и повторяем
Pessimistic Lock (пессимистичный):
- Предполагаем: конфликты ЧАСТЫЕ
- Сразу берём БЛОКИРОВКУ
- Изменяем данные
- Отпускаем блокировку
- Другие потоки ждут наше завершение
Сценарий 1: Высокая конкуренция за один ресурс
Когда много потоков пытаются одновременно обновить один и тот же объект, pessimistic lock предотвращает конфликты и пересчёты.
// ❌ Оптимистичный подход при высокой конкуренции (проблема)
public class Account {
@Version // версионирование
private Long version;
private BigDecimal balance;
}
public class TransactionService {
@Transactional
public void transferMoney(Long accountId, BigDecimal amount) {
Account account = accountRepository.findById(accountId).get();
// Между чтением и изменением прошло время
// 10 других потоков изменили account — версия не совпадает!
account.setBalance(account.getBalance().subtract(amount));
accountRepository.save(account);
// OptimisticLockException! Повторяем транзакцию...
}
}
// При 1000 параллельных переводов:
// - 999 получат исключение
// - Система перезапустит транзакцию
// - Огромное потребление ресурсов!
// ✅ Pessimistic lock (решение)
public class TransactionService {
@Transactional
public void transferMoney(Long accountId, BigDecimal amount) {
// SELECT ... FOR UPDATE — блокируем строку в БД!
Account account = accountRepository
.findByIdWithLock(accountId);
// Другие потоки ждут, пока мы закончим
account.setBalance(account.getBalance().subtract(amount));
accountRepository.save(account);
// Готово! Без переустановок транзакции
}
}
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT a FROM Account a WHERE a.id = :id")
Optional<Account> findByIdWithLock(@Param("id") Long id);
}
Сценарий 2: Бизнес-критичные операции, где конфликты дорогие
Когда cost of retry >> cost of waiting, pessimistic lock имеет смысл.
// ❌ Оптимистичный (проблема: дорогой retry)
public class InventoryService {
// Если запас низкий и много людей покупают одновременно,
// при OptimisticLock множество откатится
// Каждый retry = новая проверка запаса, расчёты доставки, etc.
@Transactional
public void reserveItem(Long itemId) {
InventoryItem item = inventoryRepository.findById(itemId).get();
if (item.getQuantity() > 0) {
item.setQuantity(item.getQuantity() - 1);
inventoryRepository.save(item);
}
}
}
// ✅ Pessimistic lock (дешевле — нет retries)
public class InventoryService {
@Transactional
public void reserveItem(Long itemId) {
// Блокируем строку — другие ждут
InventoryItem item = inventoryRepository
.findByIdWithPessimisticLock(itemId);
if (item.getQuantity() > 0) {
item.setQuantity(item.getQuantity() - 1);
inventoryRepository.save(item);
}
// Транзакция завершена успешно с первой попытки
}
}
Сценарий 3: Операции, которые нельзя повторять
Если retry может привести к побочным эффектам, используй pessimistic lock.
// ❌ Проблема: retry может отправить два письма
public class NotificationService {
@Transactional
public void sendNotification(Long userId) {
User user = userRepository.findById(userId).get();
// OptimisticLockException между этой точкой
sendEmail(user.getEmail()); // и этой
user.setLastNotificationTime(now());
userRepository.save(user);
// При retry отправляем письмо ещё раз!
}
}
// ✅ Pessimistic lock (гарантирует one-time execution)
public class NotificationService {
@Transactional
public void sendNotification(Long userId) {
User user = userRepository
.findByIdWithLock(userId); // Блокируем
sendEmail(user.getEmail()); // Выполнится один раз
user.setLastNotificationTime(now());
userRepository.save(user);
}
}
Сценарий 4: Финансовые операции
В финансовых системах нельзя рисковать конфликтами.
@Service
public class PaymentService {
@Transactional
public void processPayment(PaymentRequest request) {
// Pessimistic write lock гарантирует
// что никто другой не будет менять баланс
Account account = accountRepository
.findByIdWithWriteLock(request.getAccountId());
validateBalance(account, request.getAmount());
account.debit(request.getAmount());
accountRepository.save(account);
// Никаких race conditions, никаких потерь денег!
}
}
Виды pessimistic lock в JPA
// 1. PESSIMISTIC_READ (SELECT ... FOR SHARE)
// Несколько читателей, но писатели блокируются
@Lock(LockModeType.PESSIMISTIC_READ)
Optional<Account> findById(Long id);
// 2. PESSIMISTIC_WRITE (SELECT ... FOR UPDATE) — используй это!
// Эксклюзивная блокировка — никто другой не может читать
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Account> findById(Long id);
// 3. PESSIMISTIC_FORCE_INCREMENT
// Как PESSIMISTIC_WRITE, но ещё инкрементирует @Version
@Lock(LockModeType.PESSIMISTIC_FORCE_INCREMENT)
Optional<Account> findById(Long id);
Сценарий 5: Чтение перед изменением (Read-Modify-Write)
Когда нужно гарантированно прочитать текущее значение, используй pessimistic lock.
// ❌ Проблема: между read и write может кто-то вклиниться
Account account = accountRepository.findById(id).get();
BigDecimal newBalance = account.getBalance().add(interest);
account.setBalance(newBalance);
// Если другой поток изменил баланс между строками 1 и 3?
// ✅ Решение: pessimistic lock
Account account = accountRepository
.findByIdWithLock(id); // SELECT ... FOR UPDATE
BigDecimal newBalance = account.getBalance().add(interest);
account.setBalance(newBalance);
// Гарантия: никто не изменит во время операции
Когда ИЗБЕГАТЬ pessimistic lock:
- Низкая конкуренция — используй optimistic lock
- Много чтений, мало писаний — optimistic lock
- Долгие транзакции — блокировка будет долгой
- Распределённые системы — распределённые блокировки сложны
Ключевая таблица решений:
| Сценарий | Конкуренция | Retry стоимость | Выбор |
|---|---|---|---|
| Высокая конкуренция | Высокая | Дорого | Pessimistic |
| Низкая конкуренция | Низкая | Дёшево | Optimistic |
| Финансовые операции | Переменная | Критично | Pessimistic |
| Каталог товаров | Низкая | Дёшево | Optimistic |
| Бронирование мест | Высокая | Высоко | Pessimistic |
Правило: Используй pessimistic lock, когда конкуренция за ресурс предсказуемо высока и cost of conflict > cost of waiting.