2013-02-22 9 views
22

Ich habe einige Frage ... Beispiel: ein Benutzer etwas für seine USDPHP/MySQL - wie zwei Anfragen zu verhindern * Update

  1. prüfen sein USD Gleichgewicht
  2. Abzüglich des USD kaufen wird von seinem Konto
  3. eine Bestellung aufgeben - seine
  4. USD > Auftragswarteschlange
  5. Benutzer erhält seine Position und der andere bekommt

können sagen, die Benutzer macht 5 Anfragen in der gleichen Sekunde (sehr schnell). So ist es möglich (und passieren), dass 5 Anfragen laufen. Er hat nur Geld, um nur aus 1 Anfrage zu kaufen. Jetzt sind die Anfragen so schnell, dass das Skript sein Guthaben überprüft, aber nicht so schnell ist, dass es das Geld von seinem Konto abzieht. Also werden die Anfragen zweimal passieren! Wie löst man es?

ich LOCK in MySQL verwenden, bevor ich den Prozess zu starten:

  1. IS_FREE_LOCK - Check gibt es eine Sperre für diesen Benutzer, wenn nicht -> 2.
  2. GET_LOCK - setzt das Schloss
  3. das machen Auftrag/Transaktion
  4. RELEASE_LOCK - die Verriegelung

Aber das ist nicht wirklich funktioniert. Gibt es eine andere Art und Weise?

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)); 
} 

aktualisieren die Transaktion Funktion mit SELECT Getestet ... 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(); 
+27

[** Bitte verwenden Sie nicht mysql_ * 'Funktionen im neuen Code **] (http://bit.ly/phpmsql). Sie werden nicht mehr gewartet [und sind offiziell veraltet] (http://j.mp/XqV7Lp). Siehe die [** rote Box **] (http://j.mp/Te9zIL)? Erfahren Sie mehr über [* prepared statements *] (http://j.mp/T9hLWi) und verwenden Sie [PDO] (http://php.net/pdo) oder [MySQLi] (http://php.net/) mysqli) - [dieser Artikel] (http://j.mp/QEx8IB) wird Ihnen helfen zu entscheiden, welche. – Kermit

+3

Jeder weiß, was das bedeutet: "Der Benutzer wird sein Gegenstand und der andere wird sein USD"? Meinten sie "bekommt" anstatt "wird"? – ESRogs

+9

Wie ist es überhaupt * möglich * eine Online-Trading-Site zu betreiben, ohne auch nur die * Grundlagen * über Transaktionen zu kennen?!? – Massimo

Antwort

25

Zunächst einmal müssen Sie Transaktionen verwenden, aber das ist nicht genug. In Ihrer Transaktion können Sie SELECT FOR UPDATE verwenden.

Es ist im Grunde gesagt, "Ich werde die Datensätze aktualisieren, die ich auswähle", so dass es die gleichen Sperren setzt, die ein UPDATE setzen würde. Aber denken Sie daran, dass dies innerhalb einer Transaktion mit deaktiviertem Autocommit geschehen muss.

+0

hat ein Beispiel im Update gemacht! Funktioniert für mich, danke :) – DjangoSi

6

Verwenden TRANSACTION und wenn es ein Rollback Sie können ausfällt.

Angenommen, der aktuelle Kontostand beträgt 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 
+1

Wenn also der Benutzer 2 Anfragen stellt, werden die Transaktionen in der Warteschlange ausgeführt? Also, wenn ich TRANSACTION starten, wird die andere Anfrage so lange in die Warteschlange gestellt, wie ich commit oder Rollback? – DjangoSi

+0

Ich denke nicht, dass dies das Problem von op angeht, da die 2 selects immer noch genug USD zurückgeben. –

+0

Die Abfragen in db werden nicht gleichzeitig ausgeführt. Ohne Transaktionen werden 2 Selects in die Warteschlange gestellt und dann die Updates. Bei der Transaktion wird ein BLOCK von sql statemets in die Warteschlange gestellt, der gleichzeitig ausgeführt werden muss, bevor zur nächsten Queue-Anweisung gesprungen wird. Bei Transaktionen schlägt die 2. Auswahl fehl. – Oden

5

Dies ist, wie ich es vor vielen Jahren zu tun pflegte ..

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

(Ist das noch eine zuverlässige Methode?)

1

Sie TRANSACTION am SERIALIZABLE Isolationsstufe verwenden müssen.

0

Sie müssen die Datenrevision für MySQL UPDATE verwenden.

Verwandte Themen