Mysql
 sql >> डेटाबेस >  >> RDS >> Mysql

पीएचपी पीडीओ लेनदेन डुप्लिकेटिंग

@GarryWelding की टिप्पणी को प्रतिध्वनित करना:डेटाबेस अपडेट कोड में वर्णित उपयोग के मामले को संभालने के लिए उपयुक्त स्थान नहीं है। उपयोगकर्ता तालिका में एक पंक्ति को लॉक करना सही समाधान नहीं है।

एक कदम का बैकअप लें। ऐसा लगता है कि हम उपयोगकर्ता खरीद पर कुछ बढ़िया नियंत्रण चाहते हैं। ऐसा लगता है कि हमें उपयोगकर्ता खरीद के रिकॉर्ड को स्टोर करने के लिए एक जगह चाहिए, और फिर हम इसे देख सकते हैं।

डेटाबेस डिज़ाइन में गोता लगाए बिना, मैं यहाँ कुछ विचार प्रस्तुत करने जा रहा हूँ...

"उपयोगकर्ता" इकाई के अलावा

user
   username
   account_balance

ऐसा लगता है कि उपयोगकर्ता द्वारा की गई खरीदारियों के बारे में कुछ जानकारी में हमारी रुचि है। मैं जानकारी/विशेषताओं के बारे में कुछ सुझाव दे रहा हूं जो हमारे लिए रुचिकर हो सकते हैं, यह दावा नहीं कर रहा हूं कि ये सभी आपके उपयोग के मामले में आवश्यक हैं:

user_purchase
   username that made the purchase
   items/services purchased
   datetime the purchase was originated
   money_amount of the purchase
   computer/session the purchase was made from
   status (completed, rejected, ...)
   reason (e.g. purchase is rejected, "insufficient funds", "duplicate item"

हम किसी उपयोगकर्ता के "खाता शेष" में उस सभी जानकारी को ट्रैक करने का प्रयास नहीं करना चाहते हैं, खासकर जब से उपयोगकर्ता से कई खरीदारियां हो सकती हैं।

यदि हमारा उपयोग मामला उससे कहीं अधिक सरल है, और हम केवल एक उपयोगकर्ता द्वारा सबसे हाल की खरीदारी का ट्रैक रखते हैं, तो हम उसे उपयोगकर्ता इकाई में रिकॉर्ड कर सकते हैं।

user
  username 
  account_balance ("money")
  most_recent_purchase
     _datetime
     _item_service
     _amount ("money")
     _from_computer/session

और फिर प्रत्येक खरीदारी के साथ, हम नया खाता_बैलेंस रिकॉर्ड कर सकते हैं, और पिछली "सबसे हाल की खरीदारी" जानकारी को अधिलेखित कर सकते हैं

यदि हम केवल "एक ही समय" में कई खरीद को रोक रहे हैं, तो हमें इसे परिभाषित करने की आवश्यकता है ... क्या इसका मतलब एक ही सटीक माइक्रोसेकंड के भीतर है? 10 मिलीसेकंड के भीतर?

क्या हम केवल विभिन्न कंप्यूटरों/सत्रों से "डुप्लिकेट" खरीदारी को रोकना चाहते हैं? एक ही सत्र में दो डुप्लीकेट अनुरोधों के बारे में क्या?

यह नहीं है मैं समस्या का समाधान कैसे करूंगा। लेकिन आपके द्वारा पूछे गए प्रश्न का उत्तर देने के लिए, यदि हम एक साधारण उपयोग के मामले के साथ जाते हैं - "एक दूसरे के मिलीसेकंड के भीतर दो खरीदारी रोकें", और हम इसे UPDATE में करना चाहते हैं user . का टेबल

इस तरह की एक तालिका परिभाषा दी गई है:

user
  username                 datatype    NOT NULL PRIMARY KEY 
  account_balance          datatype    NOT NULL
  most_recent_purchase_dt  DATETIME(6) NOT NULL COMMENT 'most recent purchase dt)

उपयोगकर्ता तालिका (डेटाबेस द्वारा लौटाए गए समय का उपयोग करके) में दर्ज की गई सबसे हाल की खरीदारी के डेटाटाइम (माइक्रोसेकंड तक) के साथ

UPDATE user u
   SET u.most_recent_purchase_dt = NOW(6) 
     , u.account_balance  = u.account_balance - :money1
 WHERE u.username         = :user
   AND u.account_balance >= :money2
   AND NOT ( u.most_recent_purchase_dt >= NOW(6) + INTERVAL -1000 MICROSECOND
         AND u.most_recent_purchase_dt <  NOW(6) + INTERVAL +1001 MICROSECOND 
           )

फिर हम कथन से प्रभावित पंक्तियों की संख्या का पता लगा सकते हैं।

यदि हम शून्य पंक्तियों को प्रभावित करते हैं, तो या तो :user नहीं मिला, या :money2 खाता शेष से अधिक था, या most_recent_purchase_dt अब के +/- 1 मिलीसेकंड की सीमा के भीतर था। हम कौन सा नहीं बता सकते।

यदि शून्य से अधिक पंक्तियाँ प्रभावित होती हैं, तो हम जानते हैं कि एक अद्यतन हुआ है।

संपादित करें

कुछ प्रमुख बिंदुओं पर ज़ोर देने के लिए जिन्हें शायद नज़रअंदाज़ कर दिया गया हो...

उदाहरण SQL भिन्नात्मक सेकंड के लिए समर्थन की अपेक्षा कर रहा है, जिसके लिए MySQL 5.7 या बाद के संस्करण की आवश्यकता है। 5.6 और इससे पहले के संस्करण में, DATETIME का रिज़ॉल्यूशन केवल दूसरे से कम था। (उदाहरण तालिका में नोट कॉलम परिभाषा और SQL माइक्रोसेकंड तक रिज़ॉल्यूशन निर्दिष्ट करता है... DATETIME(6) और NOW(6)

उदाहरण SQL कथन username की अपेक्षा कर रहा है user . में प्राथमिक कुंजी या अद्वितीय कुंजी होने के लिए टेबल। यह उदाहरण तालिका परिभाषा में नोट किया गया है (लेकिन हाइलाइट नहीं किया गया है)।

उदाहरण SQL कथन user . के अद्यतन को ओवरराइड करता है एक मिलीसेकंड . के भीतर निष्पादित दो कथनों के लिए एक दूसरे की। परीक्षण के लिए, उस मिलीसेकंड रिज़ॉल्यूशन को लंबे अंतराल में बदलें। उदाहरण के लिए, इसे एक मिनट में बदलें।

यानी, 1000 MICROSECOND . की दो घटनाओं को बदलें से 60 SECOND

कुछ अन्य नोट:bindValue use का उपयोग करें bindParam . के स्थान पर (चूंकि हम कथन को मान प्रदान कर रहे हैं, कथन से मान वापस नहीं कर रहे हैं।

यह भी सुनिश्चित करें कि जब कोई त्रुटि होती है तो पीडीओ एक अपवाद फेंकने के लिए सेट होता है (यदि हम कोड में पीडीओ फ़ंक्शन से रिटर्न की जांच नहीं करने जा रहे हैं) तो कोड इसे (लाक्षणिक) पिंकी फिंगर के कोने में नहीं डाल रहा है हमारा मुंह डॉ. ईविल शैली "मुझे लगता है कि यह सब योजना के अनुसार होगा। क्या?")

# enable PDO exceptions
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$sql = "
UPDATE user u
   SET u.most_recent_purchase_dt = NOW(6) 
     , u.account_balance  = u.account_balance - :money1
 WHERE u.username         = :user
   AND u.account_balance >= :money2
   AND NOT ( u.most_recent_purchase_dt >= NOW(6) + INTERVAL -60 SECOND
         AND u.most_recent_purchase_dt <  NOW(6) + INTERVAL +60 SECOND
           )";

$sth = $dbh->prepare($sql)
$sth->bindValue(':money1', $amount, PDO::PARAM_STR);
$sth->bindValue(':money2', $amount, PDO::PARAM_STR);
$sth->bindValue(':user', $user, PDO::PARAM_STR);
$sth->execute(); 

# check if row was updated, and take appropriate action
$nrows = $sth->rowCount();
if( $nrows > 0 ) {
   // row was updated, purchase successful
} else {
   // row was not updated, purchase unsuccessful
}

और एक बिंदु पर जोर देने के लिए जो मैंने पहले कहा था, "लॉक द रो" समस्या को हल करने का सही तरीका नहीं है। और जिस तरह से मैंने उदाहरण में दिखाया है, उस तरह से जांच करने से हमें यह नहीं बताता कि खरीदारी असफल रही (अपर्याप्त धन या पिछली खरीद की निर्दिष्ट समय सीमा के भीतर।)



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. MySQL डेटाबेस में एक्सेंट सहेजें

  2. नेस्टेड क्लासेस - CustomRowMapper !! अब कोई समस्या नहीं !! - भाग ---- पहला

  3. मैं mysql में md5 पासवर्ड कैसे बनाऊं और स्टोर करूं?

  4. अनुक्रमित करें:त्रुटि:त्रुटि:तालिका 1 तालिका 2 से संबद्ध नहीं है

  5. MySQL में कोट्स वाले डेटा को कैसे स्टोर करें