2013-02-22 25 views
22

Tôi có một số câu hỏi ... dụ: người dùng sẽ mua một cái gì đó cho mìnhPHP/MySQL - làm thế nào để ngăn chặn hai yêu cầu * Cập nhật

  1. USD Trả USD Balance mình
  2. việc khấu trừ, USD từ ông tài khoản
  3. Hãy lệnh -> trật tự xếp hàng
  4. dùng được item của mình và một trong những khác được ông

USD Cho phép nói, người dùng thực hiện 5 yêu cầu trong cùng một giây (rất nhanh). Vì vậy, có thể (và xảy ra) 5 yêu cầu đang chạy. Anh ta chỉ có tiền để mua chỉ từ 1 yêu cầu. Bây giờ các yêu cầu quá nhanh, tập lệnh sẽ kiểm tra số dư của anh ấy, nhưng không quá nhanh, để trừ số tiền từ tài khoản của anh ấy. Vì vậy, các yêu cầu sẽ vượt qua hai lần! Cách giải quyết?

tôi sử dụng KHÓA trong mysql trước khi tôi bắt đầu quá trình:

  1. IS_FREE_LOCK - kiểm tra là có một khóa cho người dùng này nếu không muốn nói -> 2.
  2. GET_LOCK - bộ khóa
  3. làm cho đơn hàng/giao dịch
  4. RELEASE_LOCK - giải phóng khóa

Nhưng điều này không thực sự hiệu quả. Có cách nào khác không?

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

Cập nhật Tested chức năng giao dịch với 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(); 
+27

[** Vui lòng không sử dụng các hàm 'mysql_ *' trong mã mới **] (http://bit.ly/phpmsql). Chúng không còn được duy trì [và được chính thức ngừng sử dụng] (http://j.mp/XqV7Lp). Xem [** hộp màu đỏ **] (http://j.mp/Te9zIL)? Tìm hiểu về [* statement statements *] (http://j.mp/T9hLWi) thay vào đó, và sử dụng [PDO] (http://php.net/pdo) hoặc [MySQLi] (http://php.net/ mysqli) - [bài viết này] (http://j.mp/QEx8IB) sẽ giúp bạn quyết định cái nào. – Kermit

+3

Bất kỳ ai biết điều này có nghĩa là: "người dùng trở thành vật phẩm của anh ấy và người kia trở thành đô la của anh ấy"? Chúng có nghĩa là 'được' thay vì 'trở thành'? – ESRogs

+9

Làm thế nào là nó thậm chí có thể * để vận hành một trang web giao dịch trực tuyến mà không biết ngay cả những khái niệm cơ bản * về giao dịch?!? – Massimo

Trả lời

25

Trước hết, bạn phải sử dụng giao dịch, nhưng điều đó là không đủ. Trong giao dịch của mình, bạn có thể sử dụng SELECT FOR UPDATE.

Về cơ bản, nói rằng, "Tôi sẽ cập nhật hồ sơ tôi đang chọn", vì vậy, thiết lập cùng một khóa mà UPDATE sẽ được đặt. Nhưng hãy nhớ điều này phải xảy ra bên trong một giao dịch với tự động tắt.

+0

đã tạo một ví dụ trong bản cập nhật! Làm việc cho tôi, cảm ơn bạn :) – DjangoSi

6

Sử dụng TRANSACTION và nếu nó không thành công, bạn có thể rollback.

Ví dụ: giả sử số dư hiện tại là 20 đô la.

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

Vì vậy, nếu người dùng đặt 2 yêu cầu, các giao dịch có được thực hiện xếp hàng không? Vì vậy, khi tôi BẮT ĐẦU GIAO DỊCH, yêu cầu khác sẽ xếp hàng quá lâu, như tôi cam kết hoặc quay lại? – DjangoSi

+0

tôi không nghĩ rằng điều này giải quyết vấn đề của op, vì 2 lựa chọn vẫn sẽ trả lại đủ USD. –

+0

các truy vấn trong db không được thực hiện đồng thời. Nếu không có giao dịch, 2 lựa chọn sẽ được xếp hàng đợi và sau đó cập nhật. Với giao dịch, bạn xếp hàng BLOCK của các lệnh statlets sql tha phải được thực hiện đồng thời trước khi nhảy đến câu lệnh xếp hàng tiếp theo. Vì vậy, với các giao dịch lựa chọn thứ 2 sẽ thất bại. – Oden

5

Đây là cách tôi đã sử dụng để làm điều đó nhiều năm trước đây ..

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

(Đây có phải là vẫn còn là một phương pháp đáng tin cậy?)

1

bạn cần phải sử dụng giao dịch tại mức cô lập serializable.

0

Bạn cần sử dụng Sửa đổi dữ liệu cho CẬP NHẬT MySQL.

Các vấn đề liên quan