У меня есть вопрос … Например: пользователь купит что-то за свой доллар
Допустим, что пользователи делают 5 запросов за одну секунду (очень быстро). Таким образом, возможно (и произойдет), что запущено 5 запросов. У него есть только деньги, чтобы купить только с 1 запроса. Теперь запросы выполняются так быстро, что скрипт проверяет его баланс, но не так быстро, что он вычитает деньги со своего счета. Поэтому запросы будут проходить два раза! Как его решить?
Я использую LOCK в mysql перед тем, как начать процесс:
Но это не работает. Есть ли другой способ?
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();
Во-первых, вы должны использовать транзакции, но этого недостаточно. В вашей транзакции вы можете использовать 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.