Как решить условия гонки в системе бронирования
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 — «ИСТИНА». Алиса и Боб одновременно считывают информацию из базы данных о том, что комната свободна. Они оба бронируют эту комнату.
Конечный результат (номер забронирован как для Алисы, так и для Боба) отличается от того, что можно было бы ожидать, если бы операторы выполнялись последовательно (либо бронирование Алисы, либо бронирование Боба было отклонено).
Для решения условий гонки в базе данных резервирования могут использоваться различные методы контроля параллелизма, такие как пессимистический контроль параллелизма и оптимистичный контроль параллелизма. Эти методы позволяют базе данных управлять одновременным доступом к общим данным контролируемым и согласованным образом и могут помочь предотвратить возникновение условий гонки.
==Обратите внимание, что в основном у нас есть только 2 шага при бронировании номера: прочитать данные и обновить их.==
==Таким образом, мы можем решать условия гонки либо на этапе чтения, либо на этапе обновления.==
Пессимистичный контроль параллелизма
Пессимистический контроль параллелизма — это метод, используемый для предотвращения условий гонки в базе данных путем блокировки данных, к которым осуществляется доступ или которые обновляются. Это гарантирует, что только один пользователь может получить доступ к данным одновременно, а другим пользователям придется ждать снятия блокировки, прежде чем они смогут получить к ним доступ.
В 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
вместо логического доступного code>, и вы не можете использовать его в предложении
WHERE
, вы также можете использовать ограничения БД.
Оригинал