Что такое optimistic locking и как избежать race conditions

9 минут чтения
Средний рейтинг статьи — 4.8

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

Например, два сотрудника одновременно открыли карточку заказа. Один изменил адрес доставки, второй — статус оплаты. Оба нажимают кнопку «Сохранить».

Если система никак не контролирует такие ситуации, одно изменение может незаметно затереть другое.

Подобные проблемы называются race conditions, а одним из самых популярных способов их предотвращения является optimistic locking.

Что такое race condition

Race condition возникает, когда итог работы системы зависит от порядка выполнения нескольких операций.

Представим простую последовательность:

Пользователь A читает запись
Пользователь B читает запись
 
A изменяет данные
B изменяет данные
 
A сохраняет
B сохраняет

Последний запрос просто перезапишет результат предыдущего.

В итоге часть изменений потеряется.

Такую ситуацию называют lost update.

Почему обычные транзакции не всегда помогают

Многие считают, что достаточно использовать транзакции.

Однако транзакция гарантирует атомарность операций внутри одного соединения с базой данных.

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

Поэтому две независимые транзакции вполне могут работать с одной и той же старой версией записи.

Если дополнительных механизмов нет, проблема остаётся.

Два подхода к блокировке

Существует два основных подхода:

  • pessimistic locking;
  • optimistic locking.

При pessimistic locking запись блокируется сразу после чтения.

Пока один пользователь её редактирует, остальные вынуждены ждать.

Такой подход надёжен, но плохо масштабируется при большом количестве одновременных запросов.

Optimistic locking работает иначе.

Он предполагает, что конфликты происходят редко, поэтому никого заранее не блокирует.

Проверка выполняется только в момент сохранения изменений.

Как работает optimistic locking

У каждой записи появляется дополнительное поле.

Например:

id = 15
name = "Alice"
version = 7

Когда приложение читает запись, оно запоминает значение version.

При сохранении выполняется запрос примерно такого вида:

UPDATE users
SET name = 'Alice Smith',
    version = version + 1
WHERE id = 15
  AND version = 7;

Если запись никто не изменил, условие выполнится.

База обновит строку и увеличит версию.

Если же другой пользователь уже сохранил изменения, версия станет равна 8.

Тогда запрос не изменит ни одной строки.

Приложение поймёт, что возник конфликт.

Что происходит при конфликте

Допустим, два пользователя одновременно редактируют документ.

Первый успешно сохраняет изменения.

Версия записи увеличивается.

Второй пытается сохранить устаревшие данные.

База отвечает, что ни одна строка не была обновлена.

После этого приложение может:

  • показать сообщение об ошибке;
  • предложить перечитать данные;
  • объединить изменения;
  • попросить пользователя повторить операцию.

Таким образом ни одно изменение не потеряется незаметно.

Где используется optimistic locking

Этот механизм встречается практически во всех современных ORM.

Например:

  • Hibernate;
  • JPA;
  • Entity Framework;
  • Prisma;
  • TypeORM.

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

Также optimistic locking активно применяется в REST API, CRM-системах, интернет-магазинах и редакторах документов.

Можно ли использовать timestamp

Иногда вместо номера версии используют время последнего изменения.

Например:

updated_at

Перед сохранением приложение проверяет, изменилось ли это значение.

Если запись уже была обновлена, операция отклоняется.

Однако такой подход менее надёжен.

При высокой нагрузке несколько изменений могут попасть в один и тот же момент времени, а разные серверы способны иметь небольшое расхождение часов.

Поэтому отдельный счётчик версии считается более безопасным решением.

Когда optimistic locking не подходит

Хотя этот механизм очень удобен, он подходит не для всех задач.

Например, при работе с банковскими счетами или складскими остатками конфликты происходят достаточно часто.

Если десятки клиентов одновременно покупают последний товар, постоянные отказы могут ухудшить производительность.

В подобных случаях нередко используют pessimistic locking или другие механизмы синхронизации.

Выбор зависит от характера нагрузки и требований бизнеса.

Как избежать race conditions

Optimistic locking — не единственный инструмент.

Также помогают:

  • атомарные SQL-операции;
  • ограничения уникальности;
  • транзакции;
  • правильный уровень изоляции;
  • идемпотентные операции;
  • распределённые блокировки при работе с несколькими сервисами.

Часто для максимальной надёжности используют сразу несколько механизмов.

Типичные ошибки

При реализации optimistic locking разработчики нередко допускают одинаковые ошибки:

  • забывают проверять количество обновлённых строк;
  • увеличивают версию вручную в приложении вместо базы;
  • отключают проверку версии при массовых обновлениях;
  • игнорируют конфликт и автоматически перезаписывают запись;
  • используют timestamp там, где лучше подходит отдельное поле версии.

Из-за этого механизм перестаёт выполнять свою главную задачу — предотвращать потерю данных.

Итоги

Optimistic locking — это простой и эффективный способ защититься от race conditions в системах, где одновременное изменение одних и тех же данных происходит относительно редко.

В отличие от pessimistic locking он не блокирует записи заранее, а проверяет наличие изменений только в момент сохранения. Это позволяет избежать лишних блокировок и сохранить высокую производительность приложения.

Главное правило при использовании optimistic locking — всегда учитывать возможность конфликта и корректно обрабатывать ситуацию, когда запись уже была изменена другим пользователем. Именно это позволяет избежать незаметной потери данных и сделать работу системы предсказуемой даже при высокой конкурентной нагрузке.

9 минут чтения
Средний рейтинг статьи — 4.8

Настроить мониторинг за 30 секунд

Надежные оповещения о даунтаймах. Без ложных срабатываний