Как правильно использовать транзакции и блокировки для обеспечения целостности базы данных?

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

приложение использует MySql /

2 ответов


  1. начать транзакцию
  2. выберите элементы в корзине покупок (с для блокировки обновления)

до сих пор так хорошо, это, по крайней мере, не позволит пользователю делать заказ в нескольких сеансах (несколько раз пытается проверить одну и ту же карту - хорошо иметь дело с двойными щелчками мыши.)

  1. проверьте, если элементы не были забронированы другим пользователем

как вы проверяете? С стандарт SELECT или SELECT ... FOR UPDATE? Основываясь на шаге 5, я предполагаю, что вы проверяете зарезервированный столбец на элементе или что-то подобное.

проблема здесь в том, что SELECT ... FOR UPDATE на Шаге 2 не будет применяться FOR UPDATE блокировка ко всему остальному. Это только применение к SELECTed:cart-item таблица. Основываясь на имени, это будет другая запись для каждой корзины/пользователя. Это означает,что другие транзакции не будут заблокированы от продолжения.

  1. произвести оплату
  2. обновить элементы, помечая их как зарезервированные
  3. если все прошло хорошо совершить транзакцию, откат в противном случае

следуя вышеизложенному, на основе предоставленной Вами информации, вы можете в конечном итоге несколько человек покупают один и тот же товар, если вы не используете SELECT ... FOR UPDATE на Шаге 3.

Предлагаемое Решение

  1. начать транзакцию
  2. SELECT ... FOR UPDATE в cart-item таблица.

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

обязательно завершите процесс проверки здесь, если cart-item таблица говорит, что она уже заказана.

  1. SELECT ... FOR UPDATE таблица, в которой вы записываете если товар был зарезервирован.

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

в зависимости от результата, если элементы не зарезервированы, продолжайте:

  1. UPDATE ... таблица на Шаге 3, пометив товар как зарезервированный. Сделайте любой другой INSERTs и UPDATEs вам нужно, также.

  2. произвести оплату. Выполните откат, если платежная служба говорит, что платеж не работа.

  3. запись платежа, если успех.

  4. совершают сделки

убедитесь, что вы не делаете ничего, что может потерпеть неудачу между шагами 5 и 7 (например, отправка электронных писем), иначе вы можете в конечном итоге сделать платеж без его записи, в случае, если транзакция будет откат.

Шаг 3 является важным шагом в отношении того, чтобы убедиться, что два (или более) людей не пытаются заказать тот же предмет. Если два человека попытаются, 2-й человек в конечном итоге " зависнет "на своей веб-странице, пока он обрабатывает первый. Затем, когда первый закончит, 2-й прочитает столбец "зарезервировано", и вы можете вернуть сообщение пользователю, что кто-то уже приобрел этот элемент.

оплата в транзакции или нет

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

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

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

выбрать ... Для обновления несуществующего строки

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

таким образом, убедитесь, что всегда сериализовать ваши запросы, делая SELECT ... FOR UPDATE в строке, которая, как вы знаете, существует первой. Тогда вы можете SELECT ... FOR UPDATE в строке, которая может существовать или еще не существовать. (Не пытайтесь сделать просто SELECT в строке это может существовать или не существовать, так как вы будете читать Состояние строки в момент начала транзакции, а не в момент запуска SELECT. Итак,SELECT ... FOR UPDATE на несуществующих строках все еще что-то нужно сделать, чтобы получить самую последнюю информацию, просто имейте в виду, что это не заставит другие транзакции ждать.)


1. Другой пользователь, который пытается заказать тот же элемент в то же время будет обрабатываться правильно. Его сделки T2 подождите, пока T1 делается?

да. При активной транзакции держит FOR UPDATE блокировка записи, операторы в других транзакциях, которые используют любую блокировку (SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE, DELETE) будет приостановлено до тех пор, пока не будут превышены активные транзакции или "тайм-аут ожидания блокировки".

2. Оплата через PayPal или Полоса может занять некоторое время. Не станет ли это проблемой с точки зрения производительности?

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

3. Доступность элементов будет отображаться правильно все время (элементы должны быть доступны, пока проверка не завершится успешно). Если эти только для чтения выбирает использовать shared lock?

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

SELECT ... LOCK IN SHARE MODE заставит транзакцию проверки ждать, пока она не будет завершена. Это может замедлить выезды без каких-либо выплат.

4. Это возможно, что MySQL откатывает транзакцию сама по себе? Обычно лучше повторить попытку автоматически или отобразить сообщение об ошибке и позволить пользователю повторить попытку?

это возможно. Транзакция может быть откатана при превышении "тайм-аута ожидания блокировки" или при возникновении взаимоблокировки. В этом случае было бы неплохо повторить попытку автоматически.
По умолчанию приостановленные операторы завершаются ошибкой после 50s.

5. Думаю, этого достаточно, если я сделаю SELECT ... FOR UPDATE on items таблица. Таким образом, как запрос, вызванный двойным щелчком мыши, так и другому пользователю придется ждать завершения транзакции. Они будут ждать, потому что они также используют FOR UPDATE. Тем временем ваниль SELECT будет просто видеть снимок БД перед транзакцией, без задержки, правда?

да SELECT ... FOR UPDATE on items таблицы должно быть достаточно.
Да, эти выборы ждут, потому что FOR UPDATE является эксклюзивным замком.
Да, просто SELECT будет просто захватить значение, как это было до операции начавшись, это произойдет немедленно.

6. Если я использую JOIN на SELECT ... FOR UPDATE, будут ли записи в обеих таблицах заблокированы?

да SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE, DELETE блокировка всех прочитанных записей, так что мы JOIN включен. См.MySql Docs.

что интересно (по крайней мере для меня) все, что сканируется при обработке оператора SQL, блокируется, независимо от того, выбран он или нет. Например WHERE id < 10 будет блокировать также запись с id = 10!

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