Какие механизмы в базах данных существуют для борьбы с гонками данных
Что проверяют
Понимаете ли вы, откуда в веб-приложении берутся гонки (два запроса прочитали одно значение и оба записали своё) и какими средствами базы это закрывается без глобальных блокировок. Следом обычно дают задачу: списание баланса или бронирование последнего места, и спрашивают, как написать это в Django или SQLAlchemy.
Как отвечать
Идите от простого к тяжёлому:
- атомарный UPDATE: вычисление внутри одного запроса,
UPDATE accounts SET balance = balance + :delta WHERE id = :id AND balance + :delta >= 0, и проверка числа затронутых строк. В Django это.update(balance=F("balance") + delta)вместо чтения в Python иsave(); - ограничения:
UNIQUE,CHECK, внешние ключи. Дубль заказа отсечёт уникальный индекс, аINSERT ... ON CONFLICTделает upsert одной операцией; - пессимистическая блокировка строк:
SELECT ... FOR UPDATEвнутри транзакции, в Djangoselect_for_update(), в SQLAlchemywith_for_update(). Для очередей задачFOR UPDATE SKIP LOCKED; - оптимистическая блокировка: колонка
version, запись идёт с условиемWHERE version = :old. Ноль обновлённых строк означает конфликт и повтор. В SQLAlchemy для этого естьversion_id_col; - уровни изоляции: в PostgreSQL по умолчанию Read Committed, в MySQL InnoDB Repeatable Read. На Serializable PostgreSQL сам ловит аномалии, но отменяет транзакцию с кодом
40001, и код приложения обязан её повторить; - advisory locks:
pg_advisory_xact_lock(key)для блокировки по смыслу, когда строки ещё нет.
MVCC назовите как основу: читатели не блокируют писателей, но две транзакции, меняющие одну строку, всё равно встанут в очередь на её блокировке.
Где спотыкаются
Считают, что транзакция сама защищает от гонки. На Read Committed пара «прочитал, посчитал в Python, записал» внутри транзакции даёт потерянное обновление.
Предлагают LOCK TABLES или блокировку всей таблицы. Это убивает параллельность, а почти всегда хватает блокировки строки.
Ставят Serializable и не пишут повтор транзакции. Под нагрузкой пользователи начинают получать ошибки вместо результата.