2013-02-22 10 views
22

मैं कुछ सवाल ... उदाहरण है: एक उपयोगकर्ता अपने अमरीकी डालरPHP/MySQL - कैसे को रोकने के लिए दो अनुरोध * अद्यतन

  1. के लिए कुछ खरीद लेंगे उसके अमरीकी डालर शेष
  2. चेक से अमरीकी डालर काट उसकी खाता
  3. एक आदेश बनाने -> आदेश कतार
  4. उपयोगकर्ता अपने आइटम हो जाता है और एक दूसरे को अपने अमरीकी डालर

कहना चलें हो जाता है, उपयोगकर्ता एक ही दूसरे (बहुत तेज़) में 5 अनुरोध करते हैं। तो यह संभव है (और होता है) कि 5 अनुरोध चल रहे हैं। उसके पास केवल 1 अनुरोध से ही खरीदने के लिए धन है। अब अनुरोध इतने तेज़ हैं कि स्क्रिप्ट उसकी शेष राशि की जांच करती है, लेकिन इतनी तेज़ नहीं है कि यह धन को अपने खाते से घटा देती है। तो अनुरोध दो बार गुजरेंगे! इसे कैसे हल करें?

मैं mysql में लॉक का उपयोग करने से पहले मैं इस प्रक्रिया को शुरू:

  1. IS_FREE_LOCK - जांच इस प्रयोक्ता के लिए एक ताला है अगर नहीं -> 2.
  2. GET_LOCK - ताला
  3. बनाने के सेट आदेश/लेन-देन
  4. RELEASE_LOCK - रिलीज ताला

लेकिन यह वास्तव में काम नहीं करता। क्या कोई और तरीका है?

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

अद्यतन चयन के साथ लेन-देन समारोह परीक्षण किया गया ... अद्यतन

$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

[** कृपया, नए कोड ** में 'mysql_ *' फ़ंक्शंस का उपयोग न करें **] (http://bit.ly/phpmsql)। वे अब बनाए रखा नहीं जा रहे हैं [और आधिकारिक तौर पर बहिष्कृत हैं] (http://j.mp/XqV7Lp)। [** लाल बॉक्स **] (http://j.mp/Te9zIL) देखें? के बारे में [* तैयार बयान *] जानें (http://j.mp/T9hLWi) के बजाय, और प्रयोग [पीडीओ] (http://php.net/pdo) या [MySQLi] (http://php.net/ mysqli) - [इस अनुच्छेद] (http://j.mp/QEx8IB) आप जो निर्णय लेने में सहायता करेगा। – Kermit

+3

कोई भी जानता है कि इसका क्या अर्थ है: "उपयोगकर्ता उसका आइटम बन जाता है और दूसरा उसका अमरीकी बन जाता है"? क्या उनका मतलब 'बनने' की बजाय 'हो जाता है'? – ESRogs

+9

लेन-देन के बारे में * मूल बातें * जानने के बिना ऑनलाइन ट्रेडिंग साइट को संचालित करने के लिए यह भी संभव है *! – Massimo

उत्तर

25

सबसे पहले, आपको लेनदेन का उपयोग करना होगा, लेकिन यह पर्याप्त नहीं है। अपने लेनदेन में, आप SELECT FOR UPDATE का उपयोग कर सकते हैं।

यह मूल रूप से कह रहा है, "मैं जो रिकॉर्ड मैं चुन रहा हूं उसे अपडेट करने जा रहा हूं", इसलिए यह उसी ताले को सेट कर रहा है जो UPDATE सेट होगा। लेकिन याद रखें कि ऑटोकॉमिट बंद होने के साथ लेनदेन के अंदर ऐसा होना है।

+0

अद्यतन में एक उदाहरण बना दिया है! मेरे लिए काम करता है, धन्यवाद :) – DjangoSi

6

उपयोग 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 
+1

तो यदि उपयोगकर्ता 2 अनुरोध सेट करता है, तो क्या लेन-देन कतारबद्ध होगा? ताकि जब मैं ट्रांज़ेक्शन शुरू करता हूं, तो दूसरा अनुरोध कब तक प्रतिबद्ध होगा, जैसा कि मैं प्रतिबद्ध करता हूं या रोलबैक करता हूं? – DjangoSi

+0

मुझे नहीं लगता कि यह ओप की समस्या को संबोधित करता है, क्योंकि 2 चयन अभी भी पर्याप्त अमरीकी डालर लौटाएंगे। –

+0

डीबी में क्वेरी एक साथ निष्पादित नहीं की जाती हैं। लेनदेन के बिना 2 चयन कतारबद्ध होंगे और फिर अपडेट होंगे। लेनदेन के साथ आप एसक्यूएल स्टेटमेट्स के ब्लॉक को कतारबद्ध करते हैं, अगले कतार कथन पर कूदने से पहले एक साथ निष्पादित किया जाना चाहिए। तो लेनदेन के साथ दूसरा चयन विफल हो जाएगा। – Oden

1

आप serializable अलगाव स्तर पर लेन-देन में उपयोग करने की आवश्यकता है कि कैसे मैं कई साल पहले यह करने के लिए प्रयोग किया जाता है ..

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

(यह अभी भी एक विश्वसनीय तरीका है?)।

0

आपको MySQL अद्यतन के लिए डेटा संशोधन का उपयोग करने की आवश्यकता है।

संबंधित मुद्दे