J'ai une question ... exemple. Un utilisateur achètera quelque chose pour son dollar
Disons que les utilisateurs font 5 demandes dans la même seconde (très rapide) . Il est donc possible (et arrive) que 5 demandes soient en cours d'exécution . Il n'a que de l'argent pour acheter qu'à partir de 1 demande. Maintenant, les demandes Sont si rapides que le script vérifie son solde, mais n'est pas si rapide, qu'il déduit l'argent de son compte. Donc, les demandes passeront deux fois! Comment le résoudre?
J'utilise LOCK dans MySQL avant de commencer le processus:
Mais cela ne fonctionne pas vraiment. Y a-t-il un autre moyen?
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));
}
Mise à jour Testé la fonction de transaction avec 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();
Tout d'abord, vous devez utiliser des transactions, mais cela ne suffit pas. Dans votre transaction, vous pouvez utiliser SELECT FOR UPDATE
.
En gros, cela signifie: "Je vais mettre à jour les enregistrements que je sélectionne", ce qui permet de définir les mêmes verrous que ceux définis par UPDATE
. Mais rappelez-vous que cela doit se produire dans une transaction avec autocommit désactivé.
Utilisez _ TRANSACTION et si cela échoue, vous pouvez revenir en arrière.
Par exemple, supposons que le solde actuel est de 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
C'est comme ça que je le faisais il y a plusieurs années.
results = query("UPDATE table SET value=value-5 WHERE value>=5 AND ID=1")
if (results == 1) YEY!
(Est-ce toujours une méthode fiable?)
vous devez utiliser TRANSACTION au niveau d’isolement SERIALIZABLE.
Vous devez utiliser Data revision pour MySQL UPDATE.