PostgreSQL में समवर्ती DELETE / INSERT के साथ लॉकिंग समस्या


35

यह बहुत आसान है, लेकिन मैं पीजी क्या करता है (v9.0) से चकित हूं। हम एक साधारण तालिका से शुरुआत करते हैं:

CREATE TABLE test (id INT PRIMARY KEY);

और कुछ पंक्तियाँ:

INSERT INTO TEST VALUES (1);
INSERT INTO TEST VALUES (2);

अपने पसंदीदा JDBC क्वेरी टूल (ExecuteQuery) का उपयोग करते हुए, मैं दो सत्र विंडो को db से जोड़ता हूं जहां यह तालिका रहती है। ये दोनों ही लेन-देन (यानी, ऑटो-कमिट = गलत) हैं। चलो उन्हें एस 1 और एस 2 कहते हैं।

प्रत्येक के लिए समान कोड:

1:DELETE FROM test WHERE id=1;
2:INSERT INTO test VALUES (1);
3:COMMIT;

अब, इसे धीमी गति में चलाएं, खिड़कियों में एक बार में एक को निष्पादित करें।

S1-1 runs (1 row deleted)
S2-1 runs (but is blocked since S1 has a write lock)
S1-2 runs (1 row inserted)
S1-3 runs, releasing the write lock
S2-1 runs, now that it can get the lock. But reports 0 rows deleted. HUH???
S2-2 runs, reports a unique key constraint violation

अब, यह SQLServer में ठीक काम करता है। जब S2 हटाता है, तो यह 1 पंक्ति हटाए जाने की रिपोर्ट करता है। और फिर S2 का इन्सर्ट ठीक काम करता है।

मुझे संदेह है कि PostgreSQL उस तालिका में सूचकांक को लॉक कर रहा है जहां वह पंक्ति मौजूद है, जबकि SQLServer वास्तविक कुंजी मान को लॉक करता है।

क्या मैं सही हू? क्या यह काम करने के लिए बनाया जा सकता है?

जवाबों:


39

मैट और इरविन दोनों सही हैं, और मैं केवल एक और उत्तर का विस्तार कर रहा हूं कि उन्होंने इस तरह से क्या कहा जो एक टिप्पणी में फिट नहीं होगा। चूंकि उनके जवाब सभी को संतुष्ट नहीं करते हैं, और एक सुझाव था कि पोस्टग्रेक्यूएल डेवलपर्स से परामर्श किया जाना चाहिए, और मैं एक हूं, मैं विस्तार से बताऊंगा।

यहाँ महत्वपूर्ण बिंदु यह है कि SQL मानक के तहत, लेन READ COMMITTED-देन अलगाव स्तर पर चल रहे लेन-देन के भीतर , प्रतिबंध यह है कि अप्रयुक्त लेनदेन का कार्य दिखाई नहीं देना चाहिए। जब प्रतिबद्ध लेनदेन का काम दिखाई देता है तो कार्यान्वयन-निर्भर होता है। आप जो इंगित कर रहे हैं, वह इस बात में अंतर है कि दो उत्पादों ने इसे लागू करने के लिए कैसे चुना है। न तो कार्यान्वयन मानक की आवश्यकताओं का उल्लंघन कर रहा है।

यहां पोस्टग्रेक्यूएल के भीतर क्या होता है, विस्तार से देखें:

S1-1 रन (1 पंक्ति हटाई गई)

पुरानी पंक्ति को छोड़ दिया गया है, क्योंकि S1 अभी भी वापस आ सकता है, लेकिन S1 अब पंक्ति पर एक लॉक रखता है, ताकि पंक्ति को संशोधित करने का प्रयास करने वाला कोई अन्य सत्र यह देखने के लिए प्रतीक्षा करेगा कि क्या S1 वापस आता है या रोल करता है। तालिका की कोई भी रीड अभी भी पुरानी पंक्ति देख सकती है, जब तक कि वे इसके साथ लॉक करने का प्रयास न करें SELECT FOR UPDATEया SELECT FOR SHARE

S2-1 रन (लेकिन अवरुद्ध है क्योंकि S1 में राइट लॉक है)

S2 को अब S1 के परिणाम देखने के लिए इंतजार करना होगा। यदि S1 को प्रतिबद्ध होने के बजाय वापस रोल करना था, तो S2 पंक्ति को हटा देगा। ध्यान दें कि यदि S1 वापस रोल करने से पहले एक नया संस्करण सम्मिलित करता है, तो नया संस्करण कभी भी किसी अन्य लेनदेन के परिप्रेक्ष्य से नहीं होगा, और न ही पुराने संस्करण को किसी अन्य लेनदेन के परिप्रेक्ष्य से हटा दिया जाएगा।

S1-2 रन (1 पंक्ति सम्मिलित)

यह पंक्ति पुरानी से स्वतंत्र है। यदि id = 1 के साथ पंक्ति का अद्यतन किया गया था, तो पुराने और नए संस्करण संबंधित होंगे, और S2 पंक्ति के अद्यतन संस्करण को तब हटा सकता है जब यह अनब्लॉक हो गया हो। कि एक नई पंक्ति में वही मान होते हैं जो अतीत में मौजूद कुछ पंक्ति के रूप में नहीं होते हैं, लेकिन यह उस पंक्ति के अद्यतित संस्करण के समान नहीं होता है।

S1-3 रन, राइट लॉक जारी करना

इसलिए S1 के परिवर्तन कायम हैं। एक पंक्ति चली गई है। एक पंक्ति जोड़ दी गई है।

S2-1 रन, अब जब कि यह ताला प्राप्त कर सकते हैं। लेकिन 0 पंक्तियों को हटा दिया गया। हुह ???

आंतरिक रूप से क्या होता है, यह है कि अद्यतन होने पर उसी पंक्ति के अगले संस्करण में एक पंक्ति के एक संस्करण से एक सूचक होता है। यदि पंक्ति हटा दी जाती है, तो अगला संस्करण नहीं है। जब एक READ COMMITTEDलेन-देन एक ब्लॉक से एक लेखन संघर्ष पर जागता है, तो यह उस अद्यतन श्रृंखला का अंत तक अनुसरण करता है; यदि पंक्ति को हटाया नहीं गया है और यदि यह अभी भी क्वेरी के चयन मानदंडों को पूरा करती है तो इसे संसाधित किया जाएगा। यह पंक्ति हटा दी गई है, इसलिए S2 की क्वेरी आगे बढ़ती है।

तालिका के स्कैन के दौरान नई पंक्ति में S2 मिल सकता है या नहीं। यदि ऐसा होता है, तो यह देखा जाएगा कि नई पंक्ति S2 के DELETEकथन के प्रारंभ होने के बाद बनाई गई थी , और इसलिए यह दिखाई देने वाली पंक्तियों के सेट का हिस्सा नहीं है।

यदि PostgreSQL एक नए स्नैपशॉट के साथ शुरुआत से S2 के पूरे DELETE कथन को पुनः आरंभ करने के लिए था, तो यह SQL सर्वर के समान व्यवहार करेगा। PostgreSQL समुदाय ने प्रदर्शन कारणों से ऐसा करने के लिए नहीं चुना है। इस साधारण मामले में आपको कभी भी प्रदर्शन में अंतर नजर नहीं आएगा, लेकिन DELETEजब आप अवरुद्ध होने पर दस लाख पंक्तियों में थे , तो आप निश्चित रूप से होंगे। यहां व्यापार बंद है जहां PostgreSQL ने प्रदर्शन को चुना है, क्योंकि तेज संस्करण अभी भी मानक की आवश्यकताओं का अनुपालन करता है।

S2-2 रन, एक अद्वितीय प्रमुख बाधा उल्लंघन की रिपोर्ट करता है

बेशक, पंक्ति पहले से मौजूद है। यह तस्वीर का कम से कम आश्चर्यजनक हिस्सा है।

जबकि यहाँ कुछ आश्चर्यजनक व्यवहार है, सब कुछ SQL मानक के अनुरूप है और मानक के अनुसार "कार्यान्वयन-विशिष्ट" है। यह निश्चित रूप से आश्चर्य की बात हो सकती है यदि आप यह मान रहे हैं कि कुछ अन्य कार्यान्वयन का व्यवहार सभी कार्यान्वयनों में मौजूद होगा, लेकिन PostgreSQL READ COMMITTEDअलगाव स्तर में क्रमिक असफलता से बचने के लिए बहुत प्रयास करता है, और कुछ व्यवहारों को अनुमति देता है जो इसे प्राप्त करने के लिए अन्य उत्पादों से भिन्न होते हैं।

अब, व्यक्तिगत रूप से मैं किसी भी उत्पाद के कार्यान्वयन READ COMMITTEDमें लेनदेन अलगाव स्तर का बड़ा प्रशंसक नहीं हूं । वे सभी दौड़ की स्थिति को एक व्यवहारिक दृष्टिकोण से आश्चर्यजनक व्यवहार बनाने की अनुमति देते हैं। एक बार जब कोई व्यक्ति किसी उत्पाद द्वारा अनुमत अजीब व्यवहार का आदी हो जाता है, तो वे उस "सामान्य" और दूसरे उत्पाद द्वारा चुने गए व्यापार-बंदों पर विचार करते हैं। लेकिन हर उत्पाद को किसी भी मोड के लिए किसी प्रकार का व्यापार बंद करना पड़ता है जो वास्तव में लागू नहीं होता है । जहां PostgreSQL डेवलपर्स ने अवरुद्ध को कम करने के लिए लाइन को चुनना चुना है (रीड को ब्लॉक नहीं करता है और राइट को ब्लॉक नहीं करता है पढ़ता है) और क्रमिक विफलता की संभावना को कम करने के लिए।SERIALIZABLEREAD COMMITTED

मानक के लिए आवश्यक है कि SERIALIZABLEलेन-देन डिफ़ॉल्ट हो, लेकिन अधिकांश उत्पाद ऐसा नहीं करते क्योंकि यह अधिक लचर लेन-देन अलगाव स्तरों पर प्रदर्शन के कारण होता है। कुछ उत्पादों को वास्तव में अनुक्रमिक लेनदेन प्रदान नहीं किया SERIALIZABLEजाता है जब चुना जाता है - सबसे उल्लेखनीय Oracle और 9.1 से पहले PostgreSQL के संस्करण। लेकिन वास्तव में SERIALIZABLEलेन-देन का उपयोग करना दौड़ की परिस्थितियों से आश्चर्यजनक प्रभावों से बचने का एकमात्र तरीका है, और दौड़ की स्थिति SERIALIZABLEसे बचने के लिए लेनदेन को हमेशा अवरुद्ध करना चाहिए या विकासशील दौड़ की स्थिति से बचने के लिए कुछ लेनदेन को वापस करना चाहिए। SERIALIZABLEलेन-देन का सबसे आम कार्यान्वयन स्ट्रिक्ट टू-फेज़ लॉकिंग (S2PL) है जिसमें अवरुद्ध और क्रमिक असफलता (गतिरोध के रूप में) दोनों हैं।

पूर्ण प्रकटीकरण: मैंने वास्तव में क्रमिक लेनदेन को जोड़ने के लिए MIT के डैन पोर्ट्स के साथ काम किया, जो Serializable Snapshot अलगाव नामक एक नई तकनीक का उपयोग करते हुए PostgreSQL संस्करण 9.1 में शामिल है।


मुझे आश्चर्य है कि अगर इस काम को करने के लिए वास्तव में सस्ता (घटिया?) तरीका है, तो दो DELETES जारी करना है, इसके बाद INSERT। मेरे सीमित (2 थ्रेड्स) परीक्षण में, यह ठीक काम करता है, लेकिन यह देखने के लिए और अधिक परीक्षण करने की आवश्यकता है कि क्या यह कई थ्रेड्स के लिए होगा।
डेवीबेक

जब तक आप READ COMMITTEDलेन-देन का उपयोग कर रहे हैं , तब तक आपके पास एक दौड़ की स्थिति है: क्या होगा यदि दूसरा लेनदेन DELETEशुरू होने से पहले और दूसरी बार शुरू होने से पहले एक और लेन-देन डाला जाए DELETE? लेनदेन तुलना में कम सख्त साथ SERIALIZABLEकरीबी दौड़ की स्थिति के लिए दो मुख्य तरीके के माध्यम से कर रहे हैं को बढ़ावा देने और एक संघर्ष की (लेकिन यह है कि जब पंक्ति हटाया जा रहा है मदद नहीं करता है) भौतिकीकरण संघर्ष की। आप "आईडी" तालिका होने से संघर्ष को रोक सकते हैं जिसे हटाए गए प्रत्येक पंक्ति के लिए अपडेट किया गया था, या तालिका को स्पष्ट रूप से लॉक करके। या त्रुटि पर रिट्रीट का उपयोग करें।
कृतघ्न

यह है बहुमूल्य अंतर्दृष्टि के लिए बहुत बहुत धन्यवाद!
डेविबॉब

21

मेरा मानना ​​है कि यह डिज़ाइन द्वारा है, PostgreSQL 9.2 के लिए पढ़े गए अलगाव स्तर के विवरण के अनुसार :

UPDATE, DELETE, SELECT FOR UPDATE, और SELECT FOR SHARE कमांड्स लक्ष्य पंक्तियों की खोज के मामले में SELECT के समान व्यवहार करते हैं: वे केवल वही लक्ष्य पंक्तियाँ पाएँगे जो कि कमांड स्टार्ट टाइम 1 के रूप में प्रतिबद्ध थीं । हालाँकि, इस तरह की लक्ष्य पंक्ति को पहले से ही किसी अन्य समवर्ती लेनदेन द्वारा अद्यतन (या हटा दिया गया या बंद) किया जा सकता है। इस स्थिति में, अप-टू-अपडेटर कमिटमेंट करने या वापस रोल करने के लिए पहले अद्यतन लेनदेन की प्रतीक्षा करेगा (यदि यह अभी भी प्रगति में है)। यदि पहला अपडेटर वापस आ जाता है, तो इसके प्रभाव को नकार दिया जाता है और दूसरा अपडेटर मूल रूप से पाई गई पंक्ति को अपडेट करने के साथ आगे बढ़ सकता है। यदि पहला अपडेटर शुरू होता है, तो दूसरा अपडाउनटर पंक्ति को अनदेखा कर देगा यदि पहला अपडैटर इसे 2 हटा देता है, अन्यथा यह पंक्ति के अद्यतन संस्करण में अपने ऑपरेशन को लागू करने का प्रयास करेगा।

पंक्ति आप में डालने S1का अस्तित्व नहीं था अभी तक जब S2's DELETEशुरू कर दिया। इसलिए इसे ऊपर S2( 1 ) के अनुसार डिलीट करके नहीं देखा जाएगा । एक है कि S1नष्ट कर दिया द्वारा नजरअंदाज कर दिया है S2की DELETE(के अनुसार 2 )।

इसलिए S2, हटाएं कुछ नहीं करता है। जब डालने हालांकि साथ आता है, एक है कि करता है देखने S1के डालने:

क्योंकि रीड कमिटेड मोड प्रत्येक कमांड को एक नए स्नैपशॉट के साथ शुरू करता है जिसमें उस तत्काल तक किए गए सभी लेनदेन शामिल हैं, उसी लेनदेन में बाद के आदेश किसी भी मामले में प्रतिबद्ध समवर्ती लेनदेन के प्रभावों को देखेंगे । ऊपर दिए गए मुद्दे पर मुद्दा यह है कि क्या कोई एकल कमांड डेटाबेस का एक बिल्कुल सुसंगत दृश्य देखता है या नहीं।

अतः S2बाधा डालने के साथ ही सम्मिलित प्रयास विफल हो जाता है।

उस दस्तावेज़ को पढ़ना, लगातार पढ़ने या यहां तक ​​कि सीरियल करने योग्य का उपयोग करना आपकी समस्या को पूरी तरह से हल नहीं करेगा - दूसरा सत्र हटाए जाने पर क्रमांकन त्रुटि के साथ विफल हो जाएगा।

यह आपको लेनदेन को पुन: प्रयास करने की अनुमति देगा।


धन्यवाद मैट। जबकि ऐसा लगता है कि क्या हो रहा है, उस तर्क में एक दोष लगता है। यह मुझे लगता है कि, एक READ_COMMITTED आईएसओ स्तर में है, तो इन दो कथनों को एक tx के अंदर सफल होना चाहिए : DELETE FROM test WHERE ID = 1 INSERT INTO test VALUES (1) से मेरा मतलब है, यदि मेरा मतलब है कि मैं पंक्ति हटाता हूं और फिर पंक्ति सम्मिलित करता हूं। तब वह इन्सर्ट सफल होना चाहिए। SQLServer को यह अधिकार मिलता है। जैसा कि यह है, मैं एक उत्पाद में इस स्थिति से निपटने में बहुत कठिन समय बिता रहा हूं, जिसमें दोनों डेटाबेस के साथ काम करना है।
डेविबेक

11

मैं @ मैट के शानदार जवाब से पूरी तरह सहमत हूं । मैं केवल एक और उत्तर लिखता हूं, क्योंकि यह एक टिप्पणी में फिट नहीं होगा।

आपकी टिप्पणी के जवाब में: DELETES2 में पहले से ही एक विशेष पंक्ति संस्करण पर हुक दिया गया है। चूंकि इस बीच S1 द्वारा मार दिया जाता है, इसलिए S2 खुद को सफल मानता है। हालांकि एक त्वरित नज़र से स्पष्ट नहीं है, घटनाओं की श्रृंखला लगभग इस तरह है:

   S1 DELETE सफल  
S2 DELETE (प्रॉक्सी द्वारा सफल - S1 से DELETE)  
   S1 पुनः INSERTs ने इस बीच वस्तुतः मूल्य को हटा दिया  
S2 INSERT अद्वितीय कुंजी बाधा उल्लंघन के साथ विफल रहता है

यह सब डिजाइन द्वारा है। आपको वास्तव में SERIALIZABLEअपनी आवश्यकताओं के लिए लेनदेन का उपयोग करने की आवश्यकता है और सुनिश्चित करें कि आप क्रमिक विफलता पर पुन: प्रयास करें।


1

एक DEFERRABLE प्राथमिक कुंजी का उपयोग करें और पुनः प्रयास करें।


टिप के लिए धन्यवाद, लेकिन DEFERRABLE का उपयोग करने से कोई फर्क नहीं पड़ा। डॉक्टर को जैसा होना चाहिए वैसा ही पढ़ता है, लेकिन ऐसा नहीं है।
डेविबॉब

-2

हमने इस मुद्दे का भी सामना किया। हमारा समाधान select ... for updateपहले जोड़ रहा है delete from ... where। आइसोलेशन स्तर रीड कमिटेड होना चाहिए।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.