← Назад к вопросам
Когда выгоднее использовать 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;
}
Как это работает:
- Читаем запись вместе с версией (version = 1)
- Модифицируем запись
- При сохранении проверяем: версия в БД == версия которую мы читали?
- Если совпадает - обновляем (version станет 2)
- Если не совпадает - выбрасываем 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
| Аспект | Optimistic | Pessimistic |
|---|---|---|
| Блокировка | Нет | Да |
| Низкоконфликтные данные | ✅ Отлично | Чрезмерно |
| Высококонфликтные данные | ❌ Плохо | ✅ Отлично |
| Пропускная способность | Высокая | Средняя-низкая |
| 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 только если видим реальные проблемы.