Наилучший подход, когда речь идет о параллелизме PHP

#php #mysql #sql #sql-update #where-clause

#php #mysql #sql #sql-обновление #where-предложение

Вопрос:

Я работаю над проектом php mysql, где мы используем столбец баланса, который используется для отслеживания баланса пользователя. Всякий раз, когда пользователь совершает покупку, с его баланса списывается. Однако я пытаюсь выяснить, какой наилучший способ — обрабатывать несколько запросов от одного и того же пользователя при попытке приобрести продукт. Я провел некоторое исследование и написал следующие 2 подхода, я не совсем вижу разницы здесь, кто-нибудь может объяснить, в чем разница между этими двумя подходами и какой из них лучше всего подходит для параллелизма?

 Approach 1:
START transaction
"SELECT balance from users where user_id = 1 FOR UPDATE"

check if balance - product price is enough then UPDATE
commit

Approach 2:
"UPDATE users set balance = balance - 30 where user_id = 1 AND balance - 30 >= 0"
  

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

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

Ответ №1:

Любое решение окончательно предотвратит условия гонки. Базы данных SQL обладают свойствами атомарности, согласованности, изоляции и долговечности (ACID). Отличная вещь в базах данных SQL: вам не нужно беспокоиться об условиях гонки, если вы правильно выполняете транзакции.

Операции с одним оператором, подобные UPDATE во втором примере, всегда являются атомарными транзакциями. И явная транзакция в вашем (правильно написанном) первом примере также является атомарной транзакцией.

Как и @GMB, я предпочитаю использовать операции с одним оператором везде, где это возможно. Но это просто потому, что код легче понять следующему программисту (или мне самому в будущем). Любой подход работает.

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

Оба ваших решения поддерживают ACID.

Комментарии:

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

Ответ №2:

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

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

Из вашего приложения вы обычно запускаете запрос, а затем проверяете, была ли затронута строка. Если да, то вы знаете, что обновление было выполнено (значит, у пользователя было достаточно оснований) — в противном случае транзакция была отклонена (либо потому, что пользователь не существует, либо потому, что у него недостаточно оснований).

Комментарии:

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

2. @sharpness: да, именно это я и пытался подчеркнуть.

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

4. @sharpness: проблема с первым решением заключается в том, что другой сеанс может обновить ту же строку между запросом, который ее считывает, и тем, который ее обновляет. Это условие гонки, и чтобы избежать его, вам нужна транзакция (которая блокирует строку на более длительное время). Второй подход не имеет этой проблемы. Это единственный запрос, который выполняет то, что вы хотите, так что, на мой взгляд, лучший вариант здесь.

5. Строка блокируется при первом подходе, потому что я использую SELECT ДЛЯ ОБНОВЛЕНИЯ, это предотвращает обновление и чтение строки другими транзакциями или я ошибаюсь?