Как решить условия гонки в системе бронирования

Как решить условия гонки в системе бронирования

9 января 2023 г.

Дизайн любой системы бронирования, такой как Booking.com, Airbnb.com или даже Uber, предполагает «бронирование» объекта — продукта или услуги. Но что происходит, когда два клиента пытаются одновременно забронировать один и тот же номер в гостинице или такси? Как разрешается этот конфликт?

В этой статье мы рассмотрим условия гонки в БД и способы их решения.

Свойство ACID — Изоляция

Изоляция базы данных относится к уровню изоляции между параллельными транзакциями в базе данных. Уровни изоляции контролируют видимость и доступность данных для параллельных транзакций и могут влиять на возникновение условий гонки в базе данных. Если ваш уровень изоляции не является «сериализуемым» — возможны условия гонки.

Пример состояния гонки

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

CREATE TABLE Room (
  id INT PRIMARY KEY,
  room_number INT,
  available BOOLEAN
);

CREATE TABLE Booking (
  id INT PRIMARY KEY,
  room_id INT,
  start_date DATE,
  end_date DATE
);

Два пользователя, Алиса и Боб, пытаются одновременно забронировать один и тот же номер на совпадающие даты.

Алиса:

UPDATE Room
SET available = FALSE
WHERE id = 123;

INSERT INTO Booking (room_id, start_date, end_date)
VALUES (123, '2022-01-01', '2022-01-07');

Боб:

UPDATE Room
SET available = FALSE
WHERE id = 123;

INSERT INTO Booking (room_id, start_date, end_date)
VALUES (123, '2022-01-03', '2022-01-10');

Оба этих утверждения обновляют доступность комнаты с идентификатором 123 на «ЛОЖЬ» и вставляют бронирование в таблицу «Бронирование» для этой комнаты. Однако при одновременном выполнении этих операторов может возникнуть состояние гонки.

Например, предположим, что начальная доступность комнаты с идентификатором 123 — «ИСТИНА». Алиса и Боб одновременно считывают информацию из базы данных о том, что комната свободна. Они оба бронируют эту комнату.

Race condition

Конечный результат (номер забронирован как для Алисы, так и для Боба) отличается от того, что можно было бы ожидать, если бы операторы выполнялись последовательно (либо бронирование Алисы, либо бронирование Боба было отклонено).

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

==Обратите внимание, что в основном у нас есть только 2 шага при бронировании номера: прочитать данные и обновить их.==

==Таким образом, мы можем решать условия гонки либо на этапе чтения, либо на этапе обновления.==

places where we can handle race conditions

Пессимистичный контроль параллелизма

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

В SQL пессимистический контроль параллелизма может быть реализован с помощью оператора «SELECT ... FOR UPDATE». Этот оператор позволяет пользователю блокировать строки таблицы, к которым осуществляется доступ, и запрещает другим пользователям обновлять или блокировать эти строки до тех пор, пока блокировка не будет снята.

Чтобы реализовать пессимистичный контроль параллелизма для таблицы «Booking», пользователь может выполнить следующую инструкцию SQL:

SELECT * FROM Room WHERE id = 123 FOR UPDATE;

Этот оператор заблокирует строку с идентификатором 123 в таблице "Книга" и запретит другим пользователям доступ к этой строке или ее обновление до тех пор, пока блокировка не будет снята.

Чтобы снять блокировку, пользователь может подтвердить или отменить транзакцию:

COMMIT;  -- releases the lock
ROLLBACK;  -- releases the lock and discards any changes made to the data

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

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

Один из способов реализовать оптимистичный контроль параллелизма в системе бронирования — использовать столбец «версия» в таблице «Комната». В этом столбце можно хранить «номер версии» для каждого бронирования, который увеличивается при каждом обновлении бронирования.

ALTER TABLE Room
ADD version INT DEFAULT 1;

Затем нам нужно обновить операторы SQL для бронирования номера. Теперь заявление Алисы будет выглядеть так:

UPDATE Room
SET available = FALSE, version = version + 1
WHERE id = 123 AND version = 1;

INSERT INTO Booking (room_id, start_date, end_date, version)
VALUES (123, '2022-01-01', '2022-01-07', 1);

А у Боба будет выглядеть так:

UPDATE Room
SET available = FALSE, version = version + 1
WHERE id = 123 AND version = 1;

INSERT INTO Booking (room_id, start_date, end_date)
VALUES (123, '2022-01-03', '2022-01-10');

Если оба этих оператора выполняются одновременно, первый выполняемый оператор UPDATE увеличит «версию» комнаты с идентификатором 123 до 2, а второй оператор UPDATE завершится ошибкой, так как «версия " в предложении WHERE равно 1 (поэтому нулевые строки будут обновлены со второй транзакцией).

Это предотвратит возникновение состояния гонки, так как в таблицу "Бронирование" будет вставлено только одно бронирование, а доступность комнаты не будет обновлена ​​неправильно.

Заключение

Оптимистическая блокировка – это полезный метод, который позволяет нескольким пользователям одновременно получать доступ к данным и обновлять их, при этом обеспечивая обнаружение и разрешение конфликтов. Его можно использовать даже при использовании менее строгих уровней изоляции, таких как Read Committed, или когда операции чтения и записи выполняются в отдельных транзакциях. Однако у него есть недостаток, заключающийся в том, что при обнаружении конфликтов инициируются откаты, что может привести к тому, что текущая транзакция потеряет всю свою предыдущую работу. Это может быть дорогостоящим для системы базы данных, поскольку она должна отменить все ожидающие изменения, включая как строки таблицы, так и записи индекса. В ситуациях, когда конфликты происходят часто, пессимистическая блокировка может быть лучшим выбором, так как она снижает риск отката за счет предотвращения применения конфликтующих обновлений.

Бонус

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

UPDATE Room
SET available = FALSE WHERE id = 123 AND available = TRUE;

Если две транзакции будут выполняться параллельно, только первая из них будет успешной. Он работает аналогично управлению оптимистичным параллелизмом с полем версии для этого примера, но если у вас более сложная схема БД, например, если у вас есть int quantity вместо логического доступного, и вы не можете использовать его в предложении WHERE, вы также можете использовать ограничения БД.


Оригинал