Получить доступ

Чем оптимистическая блокировка отличается от пессимистической и когда какую выбрать?

MiddleХранение данных
Ещё спрашивают так:
  • Как работает @Version в JPA?
  • Что такое SELECT FOR UPDATE?
  • Как не допустить, чтобы два пользователя затёрли правки друг друга?
Коротко

Пессимистическая — сначала блокируем строку (SELECT ... FOR UPDATE), потом меняем; остальные ждут. Оптимистическая — не блокируем, а при записи проверяем версию: UPDATE ... WHERE version = ?; 0 строк — кто-то успел раньше, повторяем или сообщаем.

  • Пессимистическая — при частых конфликтах и коротких транзакциях
  • Оптимистическая — при редких конфликтах и долгом «думании» между чтением и записью (форма редактирования)
  • Оптимистическая работает и между HTTP-запросами (ETag, If-Match), пессимистическая — только внутри транзакции

А ещё Это тот же CAS, только в базе

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Попробуйте сами

Уровни изоляции: версии строк, снимки и аномалии

Подробно

Пессимистическая блокировка исходит из того, что конфликт вероятен:

BEGIN;
SELECT * FROM seats WHERE id = 42 FOR UPDATE;   -- другие FOR UPDATE ждут
UPDATE seats SET booked_by = 7 WHERE id = 42;
COMMIT;

Плюсы: просто, конфликт разрешается ожиданием, а не ошибкой. Минусы: блокировки держатся до конца транзакции — долгая транзакция тормозит остальных; возможны дедлоки при захвате нескольких строк в разном порядке; нельзя держать блокировку, пока пользователь смотрит на форму. Варианты: NOWAIT (сразу ошибка), SKIP LOCKED (взять следующую свободную — очереди задач в таблице).

Оптимистическая блокировка считает конфликт редким. У строки есть колонка version (или updated_at):

  1. читаем строку вместе с версией;
  2. меняем данные в приложении — сколько угодно долго;
  3. записываем с проверкой: UPDATE ... SET ..., version = version + 1 WHERE id = ? AND version = ?;
  4. обновлена 0 строк — кто-то изменил раньше: перечитать и повторить или показать пользователю конфликт.

В JPA/Hibernate это поле с @Version и исключение OptimisticLockException. В HTTP — ETag в ответе и If-Match в запросе на изменение: сервер вернёт 412 Precondition Failed, если ресурс изменился.

Как выбирать:

  • конфликты редки, чтений много — оптимистическая: нет ожиданий и дедлоков;
  • конфликты часты (горячая строка — остаток товара на распродаже) — оптимистическая будет постоянно откатываться; лучше пессимистическая или атомарный UPDATE stock SET qty = qty - 1 WHERE id = ? AND qty > 0;
  • между запросами пользователя (редактирование документа, корзина) — только оптимистическая;
  • распределённые системы: условная запись в DynamoDB, WATCH в Redis, версия ключа в etcd — та же оптимистическая идея.

Часто лучший вариант — вообще не читать перед записью: атомарное изменение одним запросом не требует ни той, ни другой блокировки.

Пример

-- оптимистическая: два администратора правят одну карточку товара
SELECT id, title, price, version FROM products WHERE id = 5;   -- version = 12
UPDATE products SET price = 990, version = 13
 WHERE id = 5 AND version = 12;     -- 1 строка — успех; 0 строк — конфликт

Что хотят услышать

  • Механика обеих: FOR UPDATE и проверка версии.
  • Критерий выбора: частота конфликтов и длина «окна».
  • Оптимистическая между HTTP-запросами (ETag/If-Match, @Version).
  • Атомарный UPDATE как альтернатива для счётчиков — плюс.

Частые ошибки

  • Думать, что оптимистическая блокировка что-то блокирует в базе.
  • Держать FOR UPDATE, пока ждём ответа внешнего сервиса или пользователя.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем