Оптимистичная и пессимистичная блокировка

Оптимистичная и пессимистичная стратегии решают одну задачу: как сохранить согласованность данных, когда несколько операций одновременно изменяют один и тот же объект. Они различаются моментом, в который система защищается от конфликта.

Пессимистичная блокировка не даёт конфликту возникнуть: запись блокируется до изменения. Оптимистичный контроль конкуренции допускает параллельную работу и проверяет конфликт в момент сохранения.

Почему возникает конфликт

Пусть баланс счёта равен 1000 ₽. Два пользователя одновременно читают это значение: первый собирается снять 300 ₽, второй — 500 ₽.

Если оба вычислят новый баланс на основе исходного значения и запишут результат без координации, операции дадут 700 ₽ и 500 ₽. Последняя запись перезапишет первую, хотя корректный баланс должен быть 200 ₽. Это классический lost update — потерянное обновление.

Для предотвращения таких ситуаций применяют механизмы конкурентного доступа.

Пессимистичная блокировка

Пессимистичная блокировка исходит из того, что конфликт вероятен. Операция заранее захватывает объект и не позволяет другим транзакциям изменить его до завершения работы.

Последовательность выглядит так:

  1. транзакция A блокирует запись счёта;
  2. транзакция B пытается изменить ту же запись и ждёт либо получает ошибку — это зависит от режима базы данных;
  3. транзакция A изменяет данные и выполняет COMMIT или ROLLBACK;
  4. блокировка снимается, и транзакция B может продолжить работу.

В SQL типичный пример — блокировка строки для изменения:

BEGIN;

SELECT *
FROM account
WHERE id = 10
FOR UPDATE;

UPDATE account
SET balance = balance - 300
WHERE id = 10;

COMMIT;

SELECT ... FOR UPDATE блокирует выбранную строку для конкурирующих операций записи до конца транзакции. Точное поведение зависит от СУБД и уровня изоляции.

Преимущества и недостатки

Пессимистичная блокировка проста для понимания и хорошо подходит, когда конфликтов много, а потеря или двойное использование ресурса недопустимы. Однако конкурирующие операции вынуждены ждать, что снижает пропускную способность. Кроме того, несколько транзакций могут образовать взаимную блокировку (deadlock).

Например, транзакция A заблокировала счёт 1 и ожидает счёт 2, а транзакция B заблокировала счёт 2 и ожидает счёт 1. СУБД обычно обнаруживает цикл и откатывает одну из транзакций; приложение должно уметь повторить такую операцию.

Пессимистичная блокировка оправдана, например, для ограниченного ресурса с высокой конкуренцией: мест на рейсе, остатков склада или операций, которым требуется строго последовательный порядок.

Оптимистичный контроль конкуренции

Оптимистичный контроль конкуренции (Optimistic Concurrency Control, OCC), который часто называют optimistic locking, предполагает, что конфликты редки. Операции читают и обрабатывают данные без длительной блокировки, а при сохранении проверяют: не изменилась ли запись после чтения.

Обычно для этого в таблице есть версия:

Account
-------
balance = 1000
version = 5

Две операции читают запись с версией 5. Первая успешно сохраняет изменение:

UPDATE account
SET balance = 700,
    version = 6
WHERE id = 10
  AND version = 5;

Запрос изменяет одну строку. Вторая операция пытается сохранить свой результат, тоже ожидая версию 5:

UPDATE account
SET balance = 500,
    version = 6
WHERE id = 10
  AND version = 5;

Но версия уже равна 6, поэтому запрос не изменит ни одной строки. Это означает, что данные успели изменить между чтением и записью. Приложение должно перечитать актуальное состояние, повторить вычисление или сообщить пользователю о конфликте.

Вместо отдельного version могут использоваться row_version, метка времени или сравнение прежних значений:

UPDATE person
SET age = 21
WHERE id = 10
  AND age = 20;

Здесь обновление произойдёт только если возраст всё ещё равен 20.

Название «оптимистичная блокировка» условно: длительная блокировка записи не устанавливается. Стратегия не предотвращает конфликт заранее, а обнаруживает его при попытке сохранить данные. СУБД всё равно может кратковременно использовать внутренние блокировки для выполнения самого UPDATE.

Оптимистичный подход подходит для приложений, где чтений значительно больше, чем записей, а одновременное редактирование одной записи происходит редко: профили пользователей, настройки, REST API, документы и многие сценарии ORM.

Сравнение

Пессимистичная блокировка Оптимистичный контроль конкуренции
Основная идея Предотвратить конфликт заранее Обнаружить конфликт при сохранении
Защита записи Захватывается до изменения Проверяется условием при UPDATE
Поведение конкурирующих операций Обычно ждут или получают ошибку Работают параллельно до записи
Цена конфликта Ожидание и риск deadlock Необходимость повторить операцию
Эффективна, когда Конфликты часты Конфликты редки
Типичный механизм SELECT ... FOR UPDATE version или условие по прежнему состоянию

Выбор стратегии

Выбор определяется не названием технологии, а характером нагрузки и требованиями к операции. Если конфликт ожидаем, а повторная попытка дорога или опасна, разумнее заранее блокировать ресурс. Если конфликт маловероятен и важно не задерживать независимые операции, обычно выгоднее оптимистичная проверка.

В обоих случаях приложению необходима явная стратегия обработки ошибок: ожидания, тайм-ауты и deadlock при пессимистичной блокировке; повторное чтение и разрешение конфликта при оптимистичном контроле.

Прокрутить вверх