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

Когда выгоднее использовать Optimistic lock?

3.0 Senior🔥 111 комментариев
#ORM и Hibernate

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

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

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

Optimistic Locking: когда это выгоднее и почему

Optimistic locking - это механизм управления конкурентностью без блокировки данных. Вместо блокировки, используется версионирование для обнаружения конфликтов.

Концепция Optimistic Locking

public class User implements Entity {
    @Id
    private Long id;
    
    private String name;
    private String email;
    
    @Version  // Каждое обновление инкрементирует версию
    private Long version;
}

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

  1. Читаем запись вместе с версией (version = 1)
  2. Модифицируем запись
  3. При сохранении проверяем: версия в БД == версия которую мы читали?
  4. Если совпадает - обновляем (version станет 2)
  5. Если не совпадает - выбрасываем OptimisticLockException

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

1. Высокий параллелизм, низкая вероятность конфликтов

public class UserProfileService {
    @Transactional
    public void updateProfile(String userId, UpdateProfileRequest req) {
        // Оптимистичная блокировка идеальна здесь
        User user = userRepository.findById(userId);
        user.setName(req.getName());
        user.setEmail(req.getEmail());
        
        try {
            userRepository.save(user); // Выброс OptimisticLockException если конфликт
        } catch (OptimisticLockException e) {
            // Редко происходит - пользователь обновляет профиль нечасто
            throw new ConflictException("Profile was modified, please try again");
        }
    }
}

Почему это выгодно:

  • Миллионы пользователей обновляют разные профили
  • Конфликты исключительно редки (разные строки)
  • Никаких блокировок = высокая throughput

2. Операции чтения преобладают

public class ArticleService {
    @Transactional
    public void incrementViewCount(String articleId) {
        Article article = articleRepository.findById(articleId);
        article.incrementViews();
        
        // Миллионы read'ов, редкие write'ы
        // Optimistic lock позволит всем читать параллельно
        // Даже если несколько write'ов будут конфликты - это OK
        articleRepository.save(article);
    }
}

3. Кэшированные данные

public class CachedEntityService {
    private Cache<String, CachedEntity> cache;
    
    @Transactional
    public void updateCachedData(String entityId, NewData data) {
        // Данные кэшированы - часто читаются, редко меняются
        CachedEntity entity = cache.get(entityId);
        entity.setData(data);
        
        // Optimistic lock идеален - конфликты редки
        try {
            entityRepository.save(entity);
            cache.put(entityId, entity);
        } catch (OptimisticLockException e) {
            // Перезагружаем из БД и пробуем снова
            entity = entityRepository.findById(entityId);
            // retry logic
        }
    }
}

Когда НЕ использовать Optimistic Locking

1. Частые конфликты (один ресурс - много потребителей)

public class OrderService {
    // ❌ Плохой выбор для Optimistic Lock
    @Transactional
    public void assignOrderToWorker(String orderId, String workerId) {
        Order order = orderRepository.findById(orderId);
        
        // 10 рабочих одновременно пытаются взять один заказ
        // 9 из них получат OptimisticLockException - пустая трата ресурсов
        order.setAssignedTo(workerId);
        orderRepository.save(order);
    }
    
    // ✅ Здесь нужен Pessimistic Lock
    @Transactional
    public void assignOrderToWorkerPessimistic(String orderId, String workerId) {
        Order order = orderRepository.findById(orderId, LockModeType.PESSIMISTIC_WRITE);
        
        // Первый получит блокировку, остальные подождут
        if (order.isUnassigned()) {
            order.setAssignedTo(workerId);
            orderRepository.save(order);
        }
    }
}

2. Критичные финансовые операции

public class AccountService {
    // ❌ Risky для Optimistic Lock
    @Transactional
    public void transfer(String fromId, String toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId);
        Account to = accountRepository.findById(toId);
        
        // Если during transfer баланс изменился - конфликт
        // Перезапуск транзакции может привести к двойному списанию
        from.withdraw(amount);
        to.deposit(amount);
        
        accountRepository.save(from);
        accountRepository.save(to);
    }
    
    // ✅ Правильный выбор - Pessimistic Lock
    @Transactional
    public void transferSafe(String fromId, String toId, BigDecimal amount) {
        Account from = accountRepository.findById(
            fromId, LockModeType.PESSIMISTIC_WRITE);
        Account to = accountRepository.findById(
            toId, LockModeType.PESSIMISTIC_WRITE);
        
        // Гарантировано никакие конкурентные операции не помешают
        from.withdraw(amount);
        to.deposit(amount);
        
        accountRepository.save(from);
        accountRepository.save(to);
    }
}

Сравнение: Optimistic vs Pessimistic Lock

АспектOptimisticPessimistic
БлокировкаНетДа
Низкоконфликтные данные✅ ОтличноЧрезмерно
Высококонфликтные данные❌ Плохо✅ Отлично
Пропускная способностьВысокаяСредняя-низкая
Deadlock'иНетВозможны
Retry логикаНужнаНе нужна
Память БДМеньшеБольше (locks)

Практическая реализация с retry

@Service
public class OptimisticLockService {
    private static final int MAX_RETRIES = 3;
    private UserRepository userRepository;
    
    @Transactional
    public void updateUserWithRetry(String userId, UpdateRequest req) {
        int attempts = 0;
        
        while (attempts < MAX_RETRIES) {
            try {
                User user = userRepository.findById(userId);
                user.apply(req);
                userRepository.save(user);
                return; // Success
                
            } catch (OptimisticLockException e) {
                attempts++;
                if (attempts >= MAX_RETRIES) {
                    throw new TooManyRetriesException(
                        "Failed to update after " + MAX_RETRIES + " attempts");
                }
                // Retry: данные переличитаются в новой транзакции
            }
        }
    }
}

Когда Optimistic Locking - оптимальный выбор

Используй когда:

  • ✅ Много читателей, мало писателей
  • ✅ Разные пользователи обновляют разные ресурсы
  • ✅ Конфликты редки и приемлемо их отловить
  • ✅ Важна высокая пропускная способность
  • ✅ Нет risk'а финансовых проблем при конфликтах

НЕ используй когда:

  • ❌ Один ресурс конкурируют много потребителей
  • ❌ Критичные операции (платежи, переводы)
  • ❌ Высокий шанс конфликтов
  • ❌ Нельзя позволить retry'и

Мой опыт показал: в 80% веб-приложений низкий процент конфликтов, поэтому Optimistic Locking - первый выбор. Переходим на Pessimistic только если видим реальные проблемы.

Когда выгоднее использовать Optimistic lock? | PrepBro