मेरे पास एक ऐसी प्रणाली है जिसमें बाहरी सिस्टम के साथ इंटरफेसिंग के लिए एक जटिल प्राथमिक कुंजी है, और आंतरिक उपयोग के लिए एक तेज़, छोटी अपारदर्शी प्राथमिक कुंजी है। उदाहरण के लिए: बाहरी कुंजी एक यौगिक मूल्य हो सकती है - कुछ (नाम दिया गया नाम (वर्कर), पारिवारिक नाम (वर्कर), ज़िप कोड (चार)) और आंतरिक कुंजी एक पूर्णांक ("ग्राहक आईडी") होगी।समेकित रूप से बिना किसी विवाद के 0 जेनरिक एसक्यूएल में नई पंक्ति को पुनर्प्राप्त करें (चुनें) या बनाएं (डालें)
जब मुझे बाहरी कुंजी के साथ आने वाला अनुरोध प्राप्त होता है, तो मुझे आंतरिक कुंजी को देखने की आवश्यकता होती है - और यहां एक मुश्किल हिस्सा है - नया आंतरिक कुंजी आवंटित करें यदि मेरे पास पहले से ही बाहरी आईडी के लिए कोई नहीं है ।
स्पष्ट रूप से यदि मेरे पास एक समय में डेटाबेस से बात करने वाला केवल एक ग्राहक है, तो यह ठीक है। SELECT customer_id FROM customers WHERE given_name = 'foo' AND ...
, तो INSERT INTO customers VALUES (...)
अगर मुझे कोई मूल्य नहीं मिलता है। लेकिन, अगर बाहरी सिस्टम से संभावित रूप से कई अनुरोध आ रहे हैं, और कई लोग पहले से अनजान ग्राहक के लिए एक बार पहुंच सकते हैं, तो एक दौड़ की स्थिति है जहां कई ग्राहक INSERT
नई पंक्ति का प्रयास कर सकते हैं।
यदि मैं मौजूदा पंक्ति को संशोधित कर रहा था, तो यह आसान होगा; करने से पहले, बस SELECT FOR UPDATE
पहले, उपयुक्त पंक्ति-स्तर लॉक प्राप्त करने के लिए। लेकिन इस मामले में, मेरे पास एक पंक्ति नहीं है जिसे मैं लॉक कर सकता हूं, क्योंकि पंक्ति अभी तक मौजूद नहीं है!
मैं अब तक कई समाधान के साथ आए है, लेकिन उनमें से प्रत्येक के कुछ बहुत महत्वपूर्ण मुद्दों है:
- पकड़ने
INSERT
पर त्रुटि, ऊपर से पूरे लेन-देन फिर से प्रयास करें। यह एक समस्या है यदि लेनदेन में एक दर्जन ग्राहक शामिल हैं, खासकर यदि आने वाले डेटा संभावित रूप से एक ही ग्राहक के बारे में हर बार एक अलग आदेश में बात कर रहे हैं। पारस्परिक रूप से रिकर्सिव डेडलॉक लूप में फंस जाना संभव है, जहां हर बार एक अलग ग्राहक पर संघर्ष होता है। आप पुन: प्रयास प्रयासों के बीच एक घातीय प्रतीक्षा समय के साथ इसे कम कर सकते हैं, लेकिन संघर्षों से निपटने के लिए यह एक धीमा और महंगा तरीका है। साथ ही, यह एप्लिकेशन कोड को थोड़ा सा जटिल करता है क्योंकि सबकुछ पुनरारंभ करने की आवश्यकता है। - savepoints का उपयोग करें।
SELECT
से पहले एक सेवपॉइंट प्रारंभ करें,INSERT
पर त्रुटि पकड़ें, और उसके बाद सहेजेंपॉइंट औरSELECT
पर फिर से रोल करें। Savepoints पूरी तरह से पोर्टेबल नहीं हैं, और उनके semantics और क्षमताओं डेटाबेस के बीच थोड़ा और संक्षेप में भिन्न; मैंने देखा है कि सबसे बड़ा अंतर यह है कि, कभी-कभी वे घोंसले लगते हैं और कभी-कभी वे नहीं करते हैं, इसलिए अगर मैं उनसे बच सकता तो यह अच्छा होगा। हालांकि यह केवल एक अस्पष्ट प्रभाव है - क्या यह गलत है? क्या savepoints मानकीकृत, या कम से कम व्यावहारिक रूप से संगत हैं? इसके अलावा, सेवपॉइंट्स समान लेनदेन पर समानांतर में चीजों को करना मुश्किल बनाता है, क्योंकि हो सकता है कि आप यह बताने में सक्षम न हों कि आप कितना काम वापस ले जाएंगे, हालांकि मुझे एहसास है कि मुझे इसके साथ रहने की आवश्यकता हो सकती है। - कुछ वैश्विक लॉक प्राप्त करें, जैसे कि LOCK कथन (oraclemysqlpostgres) का उपयोग करके तालिका-स्तर लॉक की तरह। यह स्पष्ट रूप से इन परिचालनों को धीमा कर देता है और परिणामस्वरूप बहुत सारे लॉक विवाद होते हैं, इसलिए मैं इससे बचना पसंद करूंगा।
- अधिक बढ़िया, लेकिन डेटाबेस-विशिष्ट लॉक प्राप्त करें। मैं केवल Postgres's way of doing this से परिचित हूं, जो कि निश्चित रूप से अन्य डेटाबेस में समर्थित नहीं है (फ़ंक्शन भी "
pg_
" से शुरू होते हैं) तो फिर यह एक पोर्टेबिलिटी समस्या है। इसके अलावा, पोस्टग्रेस के इस तरीके से मुझे कुंजी को किसी भी तरह की पूर्णांक में कनवर्ट करने की आवश्यकता होगी, जो यह अच्छी तरह से फिट नहीं हो सकता है। क्या hypothetical वस्तुओं के लिए ताले हासिल करने के लिए एक अच्छा तरीका है?
ऐसा लगता है कि यह डेटाबेस के साथ एक आम सहमति समस्या होनी चाहिए लेकिन मुझे इस पर बहुत सारे संसाधन नहीं मिल पाए हैं; संभवतः सिर्फ इसलिए कि मैं कैनोलिक phrasing नहीं जानता। क्या किसी भी टैग किए गए डेटाबेस में सिंटैक्स के कुछ सरल अतिरिक्त बिट के साथ ऐसा करना संभव है?
अपरर्ट/मर्ज स्टेटमेंट? –
यदि ग्राहक बाहरी कुंजी का हिस्सा है तो मुझे समस्या दिखाई नहीं दे रही है। – dkretz
यदि प्रश्न ** केवल ** टैग किया गया था 'MySQL' मैं 'INSERT ... पर लागू कुंजी' का उपयोग करने का सुझाव दूंगा। (वैकल्पिक रूप से 'ग्राहक_आईडी = LAST_INSERT_ID (customer_id) के साथ पंक्ति ग्राहक_आईडी पुनर्प्राप्त करने के लिए) –