2013-02-22 10 views
22

Mam pewne pytanie ... przykład: użytkownik kupi coś do jego USDPHP/MySQL - jak zapobiegać dwa wnioski * Aktualizacja

  1. Sprawdź swoją USD balansu
  2. odliczyć USD z jego konto
  3. Złóż zamówienie -> kolejka kolejność
  4. użytkownik dostaje swoją pozycję, a drugi ją USD

Pozwala dodawać, użytkownicy wykonują 5 żądań w tej samej sekundzie (bardzo szybko). Jest więc możliwe (i zdarza się), że 5 żądań jest uruchomionych. Ma tylko pieniądze na zakup tylko z 1 wniosku. Teraz żądania są tak szybkie, że skrypt sprawdza swoje saldo, ale nie jest tak szybki, że odejmuje on z jego konta pieniądze w wysokości . Tak więc prośby przejdą dwa razy! Jak go rozwiązać?

używam LOCK w mysql zanim zacznę tego procesu:

  1. IS_FREE_LOCK - sprawdzenie czy istnieje blokada dla tego użytkownika, jeśli nie -> 2.
  2. GET_LOCK - ustawia blokadę
  3. sprawi, że Zamówienie/transakcja
  4. RELEASE_LOCK - zwalnia blokadę

Ale to naprawdę nie działa. Czy istnieje inny sposób?

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

Aktualizacja Testowany funkcję transakcji z wybranymi ... 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

[** Proszę, nie używaj funkcji 'mysql_ *' w nowym kodzie **] (http://bit.ly/phpmsql). Nie są już utrzymywane [i są oficjalnie przestarzałe] (http://j.mp/XqV7Lp). Zobacz [** czerwone pudełko **] (http://j.mp/Te9zIL)? Dowiedz się więcej o [* przygotowanych wyciągach *] (http://j.mp/T9hLWi) i użyj [PDO] (http://php.net/pdo) lub [MySQLi] (http://php.net/ mysqli) - [ten artykuł] (http://j.mp/QEx8IB) pomoże ci zdecydować, który. – Kermit

+3

Ktoś wie, co to oznacza: "użytkownik staje się jego przedmiotem, a drugi staje się jego USD"? Czy mieli na myśli "dostaje", a nie "staje się"? – ESRogs

+9

Jak to jest * możliwe * prowadzenie witryny handlu online bez znajomości nawet * podstaw * dotyczących transakcji?!? – Massimo

Odpowiedz

25

Po pierwsze, musisz użyć transakcji, ale to za mało. W Twojej transakcji możesz użyć numeru SELECT FOR UPDATE.

Zasadniczo mówi się: "Zamierzam zaktualizować wybrane przeze mnie rekordy", więc ustawiam te same blokady, które ustawiłaby UPDATE. Pamiętaj jednak, że musi to nastąpić w przypadku transakcji z wyłączonym autocommit.

+0

zrobił przykład w aktualizacji! Działa dla mnie, dziękuję :) – DjangoSi

6

Korzystanie TRANSACTION i jeśli nie można cofnąć.

Załóżmy na przykład, że bieżące saldo wynosi 20 USD.

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

Więc jeśli użytkownik ustawił 2 żądania, czy transakcje zostaną wykonane w kolejce? Po uruchomieniu TRANSAKCJI drugie żądanie będzie czekało w kolejce tak długo, jak zatwierdzę lub wycofam? – DjangoSi

+0

Nie sądzę, że to adres problemu op, ponieważ 2 wybiera nadal będzie zwracać wystarczająco USD. –

+0

zapytania w db nie są wykonywane jednocześnie. Bez transakcji 2 zaznaczenia zostaną umieszczone w kolejce, a następnie aktualizacje. Z transakcją umieszczasz kolejkę BLOCKa w statystykach sql, które muszą być wykonywane jednocześnie przed przejściem do następnej instrukcji kolejki. Tak więc w przypadku transakcji drugie wybieranie zakończy się niepowodzeniem. – Oden

5

ten sposób użyłem to zrobić wiele lat temu ..

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

(Czy to wciąż niezawodny sposób?)

1

trzeba użyć transakcji na SERIALIZABLE poziom izolacji.

0

Musisz użyć wersji danych do aktualizacji MySQL.

Powiązane problemy