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