PHP / MySQL – как предотвратить два запроса * Обновление

У меня есть вопрос … Например: пользователь купит что-то за свой доллар

  1. Проверьте его баланс в долларах США
  2. Вычитайте USD из своего счета
  3. Сделать заказ -> очередь заказов
  4. пользователь получает свой предмет, а другой получает свой доллар

Допустим, что пользователи делают 5 запросов за одну секунду (очень быстро). Таким образом, возможно (и произойдет), что запущено 5 запросов. У него есть только деньги, чтобы купить только с 1 запроса. Теперь запросы выполняются так быстро, что скрипт проверяет его баланс, но не так быстро, что он вычитает деньги со своего счета. Поэтому запросы будут проходить два раза! Как его решить?

Я использую LOCK в mysql перед тем, как начать процесс:

  1. IS_FREE_LOCK – проверка наличия блокировки для этого пользователя, если нет -> 2.
  2. GET_LOCK – устанавливает блокировку
  3. сделать заказ / транзакцию
  4. RELEASE_LOCK – освобождает блокировку

Но это не работает. Есть ли другой способ?

function lock($id) { mysql_query("SELECT GET_LOCK('$id', 60) AS 'GetLock'"); } function is_free($id) { $query = mysql_query("SELECT IS_FREE_LOCK('$id') AS 'free'"); $row = mysql_fetch_assoc($query); if($row['free']) { return true; } else { return false; } } function release_lock($id) { mysql_query("SELECT RELEASE_LOCK('$id')"); } function account_balance($id) { $stmt = $db->prepare("SELECT USD FROM bitcoin_user_n WHERE id = ?"); $stmt->execute(array($id)); $row = $stmt->fetch(PDO::FETCH_ASSOC); return $row['USD']; } if(is_free(get_user_id())) { lock(get_user_id()); if(account_balance(get_user_id()) < str2num($_POST['amount'])) { echo "error, not enough money"; } else { $stmt = $db->prepare("UPDATE user SET USD = USD - ? WHERE id = ?"); $stmt->execute(array(str2num($_POST['amount']), get_user_id())); $stmt = $db->prepare("INSERT INTO offer (user_id, type, price, amount) VALUES (?, ?, ?, ?)"); $stmt->execute(array(get_user_id(), 2, str2num($_POST['amount']), 0)); } 

Обновление Протестировано транзакционная функция с помощью SELECT … FOR UPDATE

 $db->beginTransaction(); $stmt = $db->prepare("SELECT value, id2 FROM test WHERE id = ? FOR UPDATE"); $stmt->execute(array(1)); $row = $stmt->fetch(PDO::FETCH_ASSOC); if($row['value'] > 1) { sleep(5); $stmt = $db->prepare('UPDATE test SET value = value - 5 WHERE id = 1'); $stmt->execute(); $stmt = $db->prepare('UPDATE test SET value = value + 5 WHERE id = 2'); $stmt->execute(); echo "did have enough money"; } else { echo "no money"; } $db->commit(); 

Related of "PHP / MySQL – как предотвратить два запроса * Обновление"

Во-первых, вы должны использовать транзакции, но этого недостаточно. В вашей транзакции вы можете использовать SELECT FOR UPDATE .

В основном это говорит: «Я собираюсь обновить записи, которые я выбираю» , поэтому он устанавливает те же блокировки, что и UPDATE . Но помните, что это должно произойти внутри транзакции с отключенной автоматической записью.

Используйте TRANSACTION, и если он не сработает, вы можете откат.

Например, предположим, что текущий баланс составляет 20 долларов США.

 Connection A Connection B ======================= =========================== BEGIN TRANSACTION BEGIN TRANSACTION SELECT AccountBalance SELECT AccountBalance --returns $20 --sufficient balance, --proceed with purchase --returns $20 --sufficient balance, --proceed with purchase --update acquires exclusive lock UPDATE SET AccountBalance = AccountBalance - 20 --update blocked due UPDATE SET AccountBalance = AccountBalance - 20 --order complete COMMIT TRANSACTION --update proceeds --database triggers --constraint violation --"AccountBalance >= 0" ROLLBACK TRANSACTION 

Вот как я это делал много лет назад ..

 results = query("UPDATE table SET value=value-5 WHERE value>=5 AND ID=1") if (results == 1) YEY! 

(Это еще надежный метод?)

вам необходимо использовать транзакцию на уровне изоляции SERIALIZABLE.

Вам необходимо использовать Data revision для MySQL UPDATE.