PostgreSQL में ताले: 3. अन्य वस्तुओं को लॉक करता है

हमने पहले ही वस्तु स्तर पर कुछ तालों के बारे में बात की है (विशेष रूप से, संबंधों पर ताले के बारे में), साथ ही पंक्ति स्तर पर ताले के बारे में , वस्तु ताले और प्रतीक्षा कतार के साथ उनके संबंध, जो हमेशा ईमानदार नहीं है।

आज हमारे पास एक हौजपॉज है। चलो गतिरोधों के साथ शुरू करते हैं (वास्तव में, मैं पिछली बार उनके बारे में बात करने जा रहा था, लेकिन यह लेख अशोभनीय रूप से लंबा निकला), फिर हम शेष ऑब्जेक्ट लॉक पर जाएंगे और निष्कर्ष में विधेय ताले के बारे में बात करेंगे।

गतिरोध


ताले का उपयोग करते समय, एक गतिरोध (या गतिरोध ) स्थिति संभव है। यह तब होता है जब एक लेनदेन किसी अन्य लेनदेन द्वारा पहले से ही पकड़े गए संसाधन पर कब्जा करने का प्रयास करता है, जबकि दूसरा लेनदेन पहले पकड़े गए संसाधन को पकड़ने की कोशिश करता है। यह नीचे दिए गए आंकड़े में दर्शाया गया है: ठोस तीर कैप्चर किए गए संसाधन दिखाते हैं, धराशायी तीर पहले से ही कब्जा किए गए संसाधन पर कब्जा करने का प्रयास करते हैं।

अपेक्षाओं के ग्राफ का निर्माण करके गतिरोध की कल्पना करना सुविधाजनक है। ऐसा करने के लिए, हम विशिष्ट संसाधनों को हटाते हैं और केवल लेनदेन छोड़ते हैं, यह देखते हुए कि कौन सा लेनदेन इंतजार कर रहा है। यदि ग्राफ में समोच्च है (ऊपर से आप इसे तीर द्वारा प्राप्त कर सकते हैं) - यह एक गतिरोध है।



बेशक, गतिरोध न केवल दो लेनदेन के लिए, बल्कि किसी भी बड़ी संख्या के लिए भी संभव है।

यदि कोई गतिरोध होता है, तो इसमें शामिल लेनदेन इसके बारे में कुछ नहीं कर सकते हैं - वे अनिश्चित काल तक इंतजार करेंगे। इसलिए, सभी DBMS और PostgreSQL भी स्वचालित रूप से डेडलॉक ट्रैक करते हैं।

हालांकि, चेकिंग के लिए कुछ प्रयासों की आवश्यकता होती है, जो कि जब भी कोई नया ताला लगाने का अनुरोध किया जाता है, तब मैं नहीं करना चाहता (आखिरकार, गतिरोध काफी दुर्लभ हैं)। इसलिए, जब प्रक्रिया लॉक को पकड़ने की कोशिश करती है और नहीं कर सकती है, तो यह कतार में प्रवेश करती है और सो जाती है, लेकिन डेडलॉक_ टाइमआउट पैरामीटर (डिफ़ॉल्ट रूप से - 1 सेकंड) में निर्दिष्ट मूल्य से टाइमर शुरू करती है। यदि संसाधन पहले मुक्त हो गया है, तो अच्छा है, हमने सत्यापन पर सहेज लिया है। लेकिन अगर गतिरोध के बाद भी प्रतीक्षा जारी है, तो प्रतीक्षा प्रक्रिया को जागृत किया जाएगा और जांच शुरू की जाएगी।

यदि चेक (जिसमें उम्मीदों के ग्राफ का निर्माण और उसमें आकृति की खोज करना शामिल है) ने गतिरोधों का खुलासा नहीं किया है, तो प्रक्रिया नींद जारी है - अब पहले से ही कड़वा अंत करने के लिए।

इससे पहले की टिप्पणियों में, मुझे lock_timeout पैरामीटर के बारे में कुछ भी नहीं कहने के लिए सही रूप से फटकार लगाई गई थी , जो किसी भी ऑपरेटर पर कार्य करता है और अनिश्चित काल के लंबे इंतजार से बचा जाता है: यदि निर्दिष्ट समय में लॉक प्राप्त नहीं किया जा सकता है, तो कथन lock_not_available त्रुटि के साथ समाप्त होता है। यह स्टेटमेंट_टाइमआउट पैरामीटर के साथ भ्रमित नहीं होना चाहिए, जो कि स्टेटमेंट के कुल निष्पादन समय को सीमित करता है, भले ही वह लॉक की उम्मीद करता हो या सिर्फ काम करता हो।

यदि एक गतिरोध का पता चला है, तो लेनदेन में से एक (ज्यादातर मामलों में, जिसने चेक शुरू किया था) को जबरन समाप्त कर दिया जाता है। इस मामले में, इसके द्वारा कब्जा किए गए ताले जारी किए जाते हैं और शेष लेनदेन काम करना जारी रख सकते हैं।

डेडलॉक का आमतौर पर मतलब होता है कि एप्लिकेशन को सही तरीके से डिज़ाइन नहीं किया गया है। ऐसी स्थितियों का पता लगाने के दो तरीके हैं: सबसे पहले, संदेश सर्वर लॉग में दिखाई देंगे, और दूसरी बात, pg_stat_database.deadlocks का मान बढ़ेगा।

डेडलॉक उदाहरण


गतिरोध का एक सामान्य कारण अलग-अलग क्रम है जिसमें तालिकाओं में पंक्तियों को लॉक किया जाता है।
एक सरल उदाहरण। पहला लेनदेन 100 रूबल को पहले खाते से दूसरे में स्थानांतरित करने का इरादा रखता है। ऐसा करने के लिए, वह पहले गिनती को कम करती है:

=> BEGIN; => UPDATE accounts SET amount = amount - 100.00 WHERE acc_no = 1; 
 UPDATE 1 

उसी समय, दूसरा लेनदेन दूसरे खाते से 10 रूबल को पहले खाते में स्थानांतरित करने का इरादा रखता है। वह दूसरी गिनती को कम करके शुरू करती है:

 | => BEGIN; | => UPDATE accounts SET amount = amount - 10.00 WHERE acc_no = 2; 
 | UPDATE 1 

अब पहला लेन-देन दूसरे खाते को बढ़ाने की कोशिश कर रहा है, लेकिन पाता है कि पंक्ति बंद है।

 => UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 2; 

फिर दूसरा लेनदेन पहले खाते को बढ़ाने की कोशिश करता है, लेकिन यह भी अवरुद्ध है।

 | => UPDATE accounts SET amount = amount + 10.00 WHERE acc_no = 1; 

एक चक्रीय अपेक्षा है जो कभी भी अपने आप समाप्त नहीं होगी। एक दूसरे के बाद, पहला लेन-देन, संसाधन तक पहुंच नहीं होना, एक डेडलॉक जांच शुरू करता है और सर्वर को तोड़ देता है।

 ERROR: deadlock detected DETAIL: Process 16477 waits for ShareLock on transaction 530695; blocked by process 16513. Process 16513 waits for ShareLock on transaction 530694; blocked by process 16477. HINT: See server log for query details. CONTEXT: while updating tuple (0,2) in relation "accounts" 

अब दूसरा लेन-देन जारी रह सकता है।

 | UPDATE 1 
 | => ROLLBACK; 

 => ROLLBACK; 

इस तरह के ऑपरेशन करने का सही तरीका संसाधनों को उसी क्रम में ब्लॉक करना है। उदाहरण के लिए, इस स्थिति में, आप खातों को उनकी संख्या के आरोही क्रम में ब्लॉक कर सकते हैं।

दो अद्यतन आदेशों के लिए गतिरोध


कभी-कभी आप एक गतिरोध प्राप्त कर सकते हैं जहां, ऐसा प्रतीत होता है, यह नहीं होना चाहिए। उदाहरण के लिए, SQL कमांड को परमाणु के रूप में देखने के लिए यह सुविधाजनक और परिचित है, लेकिन अद्यतन करें - यह कमांड पंक्तियों को अपडेट करते हुए ब्लॉक करता है। ऐसा एक बार में नहीं हो रहा है। इसलिए, यदि एक कमांड एक क्रम में पंक्तियों को अपडेट करती है और दूसरे में दूसरे, तो वे गतिरोध हो सकते हैं।

ऐसी स्थिति मिलने की संभावना नहीं है, लेकिन फिर भी यह मिल सकती है। प्लेबैक के लिए, हम राशि के अवरोही क्रम में निर्मित राशि कॉलम पर एक इंडेक्स बनाएंगे:

 => CREATE INDEX ON accounts(amount DESC); 

क्या हो रहा है यह देखने के लिए समय देने के लिए, हम एक फ़ंक्शन लिखेंगे जो संचरित मूल्य को बढ़ाता है, लेकिन धीरे-धीरे, धीरे-धीरे, दूसरे के लिए:

 => CREATE FUNCTION inc_slow(n numeric) RETURNS numeric AS $$ SELECT pg_sleep(1); SELECT n + 100.00; $$ LANGUAGE SQL; 

हमें pgrowlocks एक्सटेंशन की भी आवश्यकता है।

 => CREATE EXTENSION pgrowlocks; 

पहला UPDATE कमांड पूरी तालिका को अपडेट करेगा। निष्पादन योजना स्पष्ट है - एक अनुक्रमिक स्कैन:

 | => EXPLAIN (costs off) | UPDATE accounts SET amount = inc_slow(amount); 
 | QUERY PLAN | ---------------------------- | Update on accounts | -> Seq Scan on accounts | (2 rows) 

चूंकि हमारी तालिका के पृष्ठ पर पंक्तियों के संस्करण योग के बढ़ते क्रम में हैं (ठीक उसी तरह जैसे हमने उन्हें जोड़ा), उन्हें उसी क्रम में अपडेट किया जाएगा। हम काम करने के लिए अपडेट शुरू करते हैं।

 | => UPDATE accounts SET amount = inc_slow(amount); 

इस बीच, एक और सत्र में, हम अनुक्रमिक स्कैनिंग के उपयोग पर रोक लगा देंगे:

 || => SET enable_seqscan = off; 

इस स्थिति में, शेड्यूलर निम्न अद्यतन कथन के लिए अनुक्रमणिका स्कैन का उपयोग करने का निर्णय लेता है:

 || => EXPLAIN (costs off) || UPDATE accounts SET amount = inc_slow(amount) WHERE amount > 100.00; 
 || QUERY PLAN || -------------------------------------------------------- || Update on accounts || -> Index Scan using accounts_amount_idx on accounts || Index Cond: (amount > 100.00) || (3 rows) 

दूसरी और तीसरी पंक्तियाँ स्थिति के अंतर्गत आती हैं, और, चूंकि इंडेक्स अवरोही क्रम में बनाया गया है, पंक्तियों को रिवर्स ऑर्डर में अपडेट किया जाएगा।

हमने अगला अपडेट लॉन्च किया।

 || => UPDATE accounts SET amount = inc_slow(amount) WHERE amount > 100.00; 

सारणी पृष्ठ पर एक त्वरित नज़र से पता चलता है कि पहला ऑपरेटर पहले पंक्ति (0,1) को अद्यतन करने में कामयाब रहा है, और दूसरा - अंतिम (0,3):

 => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]----------------- locked_row | (0,1) locker | 530699 <-  multi | f xids | {530699} modes | {"No Key Update"} pids | {16513} -[ RECORD 2 ]----------------- locked_row | (0,3) locker | 530700 <-  multi | f xids | {530700} modes | {"No Key Update"} pids | {16549} 

एक और दूसरा पास। पहले ऑपरेटर ने दूसरी पंक्ति को अपडेट किया, और दूसरा ऐसा करना चाहता है, लेकिन ऐसा नहीं कर सकता।

 => SELECT * FROM pgrowlocks('accounts') \gx 
 -[ RECORD 1 ]----------------- locked_row | (0,1) locker | 530699 <-  multi | f xids | {530699} modes | {"No Key Update"} pids | {16513} -[ RECORD 2 ]----------------- locked_row | (0,2) locker | 530699 <-    multi | f xids | {530699} modes | {"No Key Update"} pids | {16513} -[ RECORD 3 ]----------------- locked_row | (0,3) locker | 530700 <-  multi | f xids | {530700} modes | {"No Key Update"} pids | {16549} 

अब पहला बयान तालिका की अंतिम पंक्ति को अपडेट करना चाहेगा, लेकिन यह पहले से ही दूसरे द्वारा लॉक है। यहाँ गतिरोध है।

लेन-देन में से एक निरस्त है:

 || ERROR: deadlock detected || DETAIL: Process 16549 waits for ShareLock on transaction 530699; blocked by process 16513. || Process 16513 waits for ShareLock on transaction 530700; blocked by process 16549. || HINT: See server log for query details. || CONTEXT: while updating tuple (0,2) in relation "accounts" 

और दूसरा निष्पादन पूरा करता है:

 | UPDATE 3 

README लॉक मैनेजर में गतिरोधों का पता लगाने और उन्हें रोकने के बारे में दिलचस्प विवरण पाया जा सकता है।

यह सभी गतिरोधों के बारे में है, और हम शेष वस्तु ताले पर आगे बढ़ते हैं।



गैर-संबंध ताले


जब आप ऐसे संसाधन को लॉक करना चाहते हैं जो PostgreSQL की समझ में कोई संबंध नहीं है, तो ऑब्जेक्ट लॉक का उपयोग किया जाता है। ऐसा संसाधन लगभग कुछ भी हो सकता है: तालिका स्थान, सदस्यता, स्कीमा, भूमिकाएं, प्रगणित डेटा प्रकार ... मोटे तौर पर बोलना, सब कुछ जो सिस्टम कैटलॉग में पाया जा सकता है।

आइए एक साधारण उदाहरण देखें। हम लेन-देन शुरू करते हैं और इसमें एक तालिका बनाते हैं:

 => BEGIN; => CREATE TABLE example(n integer); 

अब देखते हैं कि pg_locks में किस प्रकार के ऑब्जेक्ट लॉक दिखाई देते हैं:

 => SELECT database, (SELECT datname FROM pg_database WHERE oid = l.database) AS dbname, classid, (SELECT relname FROM pg_class WHERE oid = l.classid) AS classname, objid, mode, granted FROM pg_locks l WHERE l.locktype = 'object' AND l.pid = pg_backend_pid(); 
  database | dbname | classid | classname | objid | mode | granted ----------+--------+---------+--------------+-------+-----------------+--------- 0 | | 1260 | pg_authid | 16384 | AccessShareLock | t 16386 | test | 2615 | pg_namespace | 2200 | AccessShareLock | t (2 rows) 

यह समझने के लिए कि वास्तव में यहां क्या अवरुद्ध है, आपको तीन फ़ील्ड देखने की आवश्यकता है: डेटाबेस, क्लासिड और ओब्सीड। पहली पंक्ति से शुरू करते हैं।

डेटाबेस उस डेटाबेस का OID है जिसमें लॉक संसाधन होता है। हमारे मामले में, इस कॉलम में शून्य है। इसका मतलब है कि हम एक वैश्विक वस्तु के साथ काम कर रहे हैं जो किसी विशेष आधार से संबंधित नहीं है।

Classid में pg_class का OID होता है, जो सिस्टम कैटलॉग टेबल के नाम से मेल खाता है, जो संसाधन के प्रकार को निर्धारित करता है। हमारे मामले में, pg_authid, अर्थात भूमिका संसाधन (उपयोगकर्ता) है।

ओब्जिड में सिस्टम कैटलॉग टेबल से ओआईडी होता है जो क्लासिड ने हमें इंगित किया था।

 => SELECT rolname FROM pg_authid WHERE oid = 16384; 
  rolname --------- student (1 row) 

इस प्रकार, छात्र की भूमिका अवरुद्ध है, जिससे हम काम कर रहे हैं।

अब दूसरी पंक्ति से निपटते हैं। डेटाबेस को इंगित किया गया है, और यह परीक्षण डेटाबेस है जिससे हम जुड़े हुए हैं।

स्कीमा में pg_namespace तालिका के लिए कक्षा बिंदु।

 => SELECT nspname FROM pg_namespace WHERE oid = 2200; 
  nspname --------- public (1 row) 

इस प्रकार, सार्वजनिक स्कीमा अवरुद्ध है।

इसलिए, हमने देखा कि किसी ऑब्जेक्ट को बनाते समय, मालिक की भूमिका और वह स्कीम जिसमें ऑब्जेक्ट बनाया जाता है, उसे ब्लॉक किया जाता है (साझा मोड में)। जो तर्कसंगत है: अन्यथा, कोई व्यक्ति भूमिका या स्कीमा हटा सकता है जबकि लेनदेन अभी तक पूरा नहीं हुआ है।

 => ROLLBACK; 

रिलेशनशिप एक्सटेंशन लॉक


जब किसी रिलेशन में पंक्तियों की संख्या (यानी टेबल, इंडेक्स, मैटेरियलाइज्ड व्यू में) बढ़ जाती है, तो PostgreSQL डालने के लिए मौजूदा पेजों में फ्री स्पेस का उपयोग कर सकता है, लेकिन, जाहिर है, कुछ बिंदु पर आपको नए पेज जोड़ने होंगे। शारीरिक रूप से, उन्हें संबंधित फ़ाइल के अंत में जोड़ा जाता है। इसे रिश्ते के विस्तार के रूप में समझा जाता है।

एक ही समय में पृष्ठों को जोड़ने के लिए दो प्रक्रियाओं को रोकने के लिए, इस प्रक्रिया को विशेष प्रकार के लॉक द्वारा संरक्षित किया जाता है। इंडेक्स को साफ करते समय एक ही लॉक का उपयोग किया जाता है ताकि स्कैनिंग के दौरान अन्य प्रक्रियाएं पेज न जोड़ सकें।

बेशक, यह ताला लेनदेन के अंत की प्रतीक्षा किए बिना जारी किया जाता है।

पहले, एक समय में केवल एक पृष्ठ पर तालिकाओं का विस्तार किया जाता था। जब एक साथ कई प्रक्रियाओं ने पंक्तियाँ सम्मिलित कीं, तो समस्याएँ हुईं, इसलिए, PostgreSQL 9.6 में, कई पृष्ठों को एक ही बार में तालिकाओं में जोड़ा गया (लॉकिंग की प्रतीक्षा कर रही प्रक्रियाओं की संख्या के अनुपात में, लेकिन 512 से अधिक नहीं)।

पेज लॉक


केवल मामले में एक पृष्ठ-स्तरीय लॉक लागू किया जाता है (विधेय ताले को छोड़कर, जिसकी चर्चा बाद में की जाती है)।

जीआईएन इंडेक्स आपको यौगिक मूल्यों में खोज को गति देने की अनुमति देता है, उदाहरण के लिए, पाठ दस्तावेजों में शब्द (या सरणियों में तत्व)। पहले सन्निकटन के लिए, ऐसे सूचकांक को एक नियमित बी-ट्री के रूप में दर्शाया जा सकता है, जिसमें दस्तावेज़ स्वयं संग्रहीत नहीं होते हैं, लेकिन इन दस्तावेजों के व्यक्तिगत शब्द। इसलिए, जब एक नया दस्तावेज़ जोड़ते हैं, तो सूचकांक को काफी दृढ़ता से पुनर्निर्माण करना पड़ता है, इसे दस्तावेज़ में शामिल प्रत्येक शब्द में प्रस्तुत करना होता है।

प्रदर्शन में सुधार करने के लिए, GIN इंडेक्स में एक विलंबित प्रविष्टि सुविधा होती है जो फास्टअपडेट स्टोरेज विकल्प द्वारा सक्षम होती है। नए शब्दों को पहले अनियंत्रित लंबित सूची में जल्दी से जोड़ा जाता है, और कुछ समय बाद, जो कुछ भी जमा हुआ है उसे मुख्य सूचकांक संरचना में स्थानांतरित कर दिया जाता है। बचत इस तथ्य के कारण है कि विभिन्न दस्तावेजों में डुप्लिकेट शब्द होने की संभावना है।

एक ही समय में प्रतीक्षा सूची से मुख्य सूचकांक में जाने से कई प्रक्रियाओं को बाहर करने के लिए, स्थानांतरण की अवधि के लिए अनन्य मोड में सूचकांक मेटा पेज को अवरुद्ध किया जाता है। यह सामान्य मोड में सूचकांक के उपयोग में हस्तक्षेप नहीं करता है।

सलाहकार के ताले


अन्य तालों (जैसे संबंध ताले) के विपरीत, सलाहकार ताले कभी भी स्वचालित रूप से सेट नहीं होते हैं, उन्हें एप्लिकेशन डेवलपर द्वारा प्रबंधित किया जाता है। वे उपयोग करने के लिए सुविधाजनक हैं, उदाहरण के लिए, अगर किसी एप्लिकेशन को किसी ऐसे उद्देश्य के लिए अवरुद्ध तर्क की आवश्यकता होती है जो साधारण तालों के मानक तर्क में फिट नहीं होता है।

मान लें कि हमारे पास एक सशर्त संसाधन है जो किसी भी डेटाबेस ऑब्जेक्ट के अनुरूप नहीं है (जिसे हम SELECT FOR या LOCK टेबल जैसे कमांड से ब्लॉक कर सकते हैं)। आपको इसके लिए एक संख्यात्मक पहचानकर्ता के साथ आने की आवश्यकता है। यदि संसाधन में एक अद्वितीय नाम है, तो एक सरल विकल्प यह हैश कोड लेना है:

 => SELECT hashtext('1'); 
  hashtext ----------- 243773337 (1 row) 

इस तरह से हम लॉक को पकड़ते हैं:

 => BEGIN; => SELECT pg_advisory_lock(hashtext('1')); 

हमेशा की तरह, लॉक जानकारी pg_locks में उपलब्ध है:

 => SELECT locktype, objid, mode, granted FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid(); 
  locktype | objid | mode | granted ----------+-----------+---------------+--------- advisory | 243773337 | ExclusiveLock | t (1 row) 

लॉक के लिए वास्तव में काम करने के लिए, संसाधन तक पहुंचने से पहले अन्य प्रक्रियाओं को भी लॉक प्राप्त करना होगा। इस नियम का अनुपालन स्पष्ट रूप से आवेदन द्वारा सुनिश्चित किया जाना चाहिए।

उपरोक्त उदाहरण में, ताला सत्र के अंत तक मान्य है, और लेन-देन नहीं, हमेशा की तरह।

 => COMMIT; => SELECT locktype, objid, mode, granted FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid(); 
  locktype | objid | mode | granted ----------+-----------+---------------+--------- advisory | 243773337 | ExclusiveLock | t (1 row) 

यह स्पष्ट रूप से जारी किया जाना चाहिए:

 => SELECT pg_advisory_unlock(hashtext('1')); 

सभी अवसरों के लिए सलाहकार तालों के साथ काम करने का एक बड़ा समूह है:

  • pg_advisory_lock_sared एक साझा लॉक को मानता है,
  • pg_advisory_xact_lock (और pg_advisory_xact_lock_sared) को लेन-देन के अंत तक लॉक मिलता है,
  • pg_try_advisory_lock (साथ ही pg_try_advisory_xact_lock और pg_try_advisory_xact_lock_sared) को लॉक प्राप्त करने की उम्मीद नहीं है, लेकिन यदि लॉक तुरंत प्राप्त नहीं किया जा सकता है तो एक गलत मान देता है।

पिछले कार्यों में सूचीबद्ध लोगों के अलावा, टास्क फ़ंक्शंस का सेट लॉक की प्रतीक्षा नहीं करने का एक और तरीका प्रदान करता है।

ताले को समर्पित करें


शब्द विधेय लॉकिंग बहुत पहले दिखाई दिया था, शुरुआती DBMSs में लॉक के आधार पर पूर्ण अलगाव को लागू करने के पहले प्रयासों में (स्तर Serializable है, हालांकि उस समय SQL मानक मौजूद नहीं था)। तब जो समस्या सामने आई थी, वह यह भी थी कि सभी पढ़ी और बदली हुई लाइनों को अवरुद्ध करने से पूर्ण अलगाव नहीं मिलता है: नई पंक्तियाँ तालिका में दिखाई दे सकती हैं जो समान चयन स्थितियों के अंतर्गत आती हैं, जिससे प्रेत होते हैं ( अलगाव पर लेख देखें) ।

विधेय तालों का विचार विधेय को अवरुद्ध करना था, न कि पंक्तियों को। यदि, जब a > 10 के साथ किसी क्वेरी को निष्पादित करते हैं, तो a > 10 को अवरुद्ध किया जाता है, इससे तालिका में नई पंक्तियाँ नहीं जुड़ेंगी जो स्थिति के अंतर्गत आती हैं और प्रेत से बचेंगी। समस्या यह है कि सामान्य मामले में यह एक कम्प्यूटेशनल रूप से मुश्किल काम है; व्यवहार में, इसे केवल उन विधेयकों के लिए हल किया जा सकता है जिनका बहुत ही सरल रूप है।

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

यह साबित होता है कि छवियों के आधार पर अलगाव असंगत रिकॉर्डिंग के एक विसंगति और केवल एक पढ़ने के लेनदेन के विसंगति की अनुमति देता है, लेकिन कोई अन्य विसंगतियां संभव नहीं हैं। यह समझने के लिए कि हम सूचीबद्ध दो विसंगतियों में से एक के साथ काम कर रहे हैं, हम लेन-देन के बीच निर्भरता का विश्लेषण कर सकते हैं और उनमें कुछ पैटर्न पा सकते हैं।

हम दो प्रकार की निर्भरताओं में रुचि रखते हैं:

  • एक लेन-देन एक पंक्ति को पढ़ता है, जिसे तब दूसरे लेनदेन (आरडब्ल्यू निर्भरता) द्वारा बदल दिया जाता है,
  • एक लेन-देन उस लाइन को संशोधित करता है जो एक और लेनदेन तब (डब्ल्यूआर निर्भरता) पढ़ता है।

डब्ल्यूआर-निर्भरता को मौजूदा पारंपरिक तालों का उपयोग करके ट्रैक किया जा सकता है, लेकिन आरडब्ल्यू-निर्भरता को केवल अतिरिक्त रूप से ट्रैक करना होगा।

मैं एक बार फिर से दोहराता हूं: नाम के बावजूद, विधेय ताले कुछ भी ब्लॉक नहीं करते हैं। इसके बजाय, जब कोई लेन-देन किया जाता है, तो एक चेक किया जाता है और, यदि निर्भरता के "खराब" अनुक्रम का पता लगाया जाता है जो एक विसंगति का संकेत दे सकता है, तो लेनदेन टूट जाता है।

आइए देखें कि विधेय ताले की स्थापना कैसे होती है। ऐसा करने के लिए, पर्याप्त संख्या में पंक्तियों और उस पर एक सूचकांक के साथ एक तालिका बनाएं।

 => CREATE TABLE pred(n integer); => INSERT INTO pred(n) SELECT gn FROM generate_series(1,10000) g(n); => CREATE INDEX ON pred(n) WITH (fillfactor = 10); => ANALYZE pred; 

यदि क्वेरी को संपूर्ण तालिका की अनुक्रमिक स्कैनिंग द्वारा निष्पादित किया जाता है, तो संपूर्ण तालिका पर विधेय लॉक सेट किया जाता है (भले ही सभी पंक्तियाँ फ़िल्टरिंग शर्तों के तहत न हों)।

 | => SELECT pg_backend_pid(); 
 | pg_backend_pid | ---------------- | 12763 | (1 row) 

 | => BEGIN ISOLATION LEVEL SERIALIZABLE; | => EXPLAIN (analyze, costs off) | SELECT * FROM pred WHERE n > 100; 
 | QUERY PLAN | ---------------------------------------------------------------- | Seq Scan on pred (actual time=0.047..12.709 rows=9900 loops=1) | Filter: (n > 100) | Rows Removed by Filter: 100 | Planning Time: 0.190 ms | Execution Time: 15.244 ms | (5 rows) 

किसी भी विधेय ताले को हमेशा एक विशेष SIReadLock (अनुक्रमिक अलगाव पढ़ें) मोड में कैप्चर किया जाता है:

 => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+----------+------+------- relation | pred | | (1 row) 

 | => ROLLBACK; 

लेकिन अगर क्वेरी को इंडेक्स स्कैनिंग का उपयोग करके निष्पादित किया जाता है, तो स्थिति बेहतर के लिए बदल जाती है। यदि हम बी-ट्री के बारे में बात करते हैं, तो यह रीड टेबल की पंक्तियों पर और सूचकांक के स्कैन किए गए पत्ती पृष्ठों पर लॉक सेट करने के लिए पर्याप्त है - जिससे हम न केवल विशिष्ट मानों को अवरुद्ध करते हैं, बल्कि पूरी रेंज पढ़ते हैं।

 | => BEGIN ISOLATION LEVEL SERIALIZABLE; | => EXPLAIN (analyze, costs off) | SELECT * FROM pred WHERE n BETWEEN 1000 AND 1001; 
 | QUERY PLAN | ------------------------------------------------------------------------------------ | Index Only Scan using pred_n_idx on pred (actual time=0.122..0.131 rows=2 loops=1) | Index Cond: ((n >= 1000) AND (n <= 1001)) | Heap Fetches: 2 | Planning Time: 0.096 ms | Execution Time: 0.153 ms | (5 rows) 

 => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+------------+------+------- tuple | pred | 3 | 236 tuple | pred | 3 | 235 page | pred_n_idx | 22 | (3 rows) 

आप कई कठिनाइयों को देख सकते हैं।

सबसे पहले, पंक्ति के प्रत्येक संस्करण के लिए एक अलग लॉक बनाया जाता है जिसे पढ़ा जाता है, लेकिन संभवतः ऐसे कई संस्करण हो सकते हैं। सिस्टम में विधेय तालों की कुल संख्या, max_pred_locks_per_transaction × max_connections पैरामीटर मानों के उत्पाद द्वारा सीमित है (डिफ़ॉल्ट मान क्रमशः 64 और 100 हैं)। इस तरह के ताले के लिए मेमोरी सर्वर स्टार्टअप पर आवंटित की जाती है; इस संख्या को पार करने का प्रयास करने के परिणामस्वरूप त्रुटियां होंगी।

इसलिए, विधेय ताले (और केवल उनके लिए!) के लिए, एक स्तर वृद्धि का उपयोग किया जाता है। PostgreSQL 10 से पहले, ऐसे प्रतिबंध थे जो कोड में कठोर थे, और इसके साथ शुरू करके आप स्तर बढ़ाकर मापदंडों को नियंत्रित कर सकते हैं। यदि पंक्ति प्रति पंक्ति संस्करण लॉक की संख्या max_pred_locks_per_page से अधिक है, तो ऐसे लॉक को एक पृष्ठ स्तर लॉक से बदल दिया जाता है। यहाँ एक उदाहरण है:

 => SHOW max_pred_locks_per_page; 
  max_pred_locks_per_page ------------------------- 2 (1 row) 

 | => EXPLAIN (analyze, costs off) | SELECT * FROM pred WHERE n BETWEEN 1000 AND 1002; 
 | QUERY PLAN | ------------------------------------------------------------------------------------ | Index Only Scan using pred_n_idx on pred (actual time=0.019..0.039 rows=3 loops=1) | Index Cond: ((n >= 1000) AND (n <= 1002)) | Heap Fetches: 3 | Planning Time: 0.069 ms | Execution Time: 0.057 ms | (5 rows) 

तीन टूक ताले के बजाय, हम एक पृष्ठ प्रकार देखते हैं:

 => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+------------+------+------- page | pred | 3 | page | pred_n_idx | 22 | (2 rows) 

इसी तरह, यदि किसी एकल संबंध से जुड़े पृष्ठ के लॉक की संख्या अधिकतम_प्रकाशित_लॉक्स_पर_संबंध से अधिक है, तो ऐसे तालों को एक संबंध स्तर के लॉक से बदल दिया जाता है।

अन्य स्तर नहीं हैं: विधेय ताले केवल रिश्तों, पृष्ठों, या पंक्ति संस्करणों के लिए कैप्चर किए जाते हैं, और हमेशा SIReadLock मोड के साथ।

बेशक, तालों के स्तर में वृद्धि अनिवार्य रूप से इस तथ्य की ओर ले जाती है कि बड़ी संख्या में लेन-देन में क्रमिक त्रुटि हो जाएगी और परिणामस्वरूप, सिस्टम थ्रूपुट में कमी आएगी। यहां आपको मेमोरी की खपत और प्रदर्शन के बीच संतुलन तलाशने की जरूरत है।

दूसरी कठिनाई यह है कि सूचकांक के साथ विभिन्न कार्यों में (उदाहरण के लिए, नई लाइनों को सम्मिलित करते समय सूचकांक पृष्ठों के विभाजन के कारण), रीड रेंज को कवर करने वाले शीट पृष्ठों की संख्या बदल सकती है। लेकिन इसे लागू करने में ध्यान दिया जाता है:

 => INSERT INTO pred SELECT 1001 FROM generate_series(1,1000); => SELECT locktype, relation::regclass, page, tuple FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763; 
  locktype | relation | page | tuple ----------+------------+------+------- page | pred | 3 | page | pred_n_idx | 211 | page | pred_n_idx | 212 | page | pred_n_idx | 22 | (4 rows) 

 | => ROLLBACK; 

वैसे, लेन-देन पूरा होने के तुरंत बाद विधेय ताले को हमेशा के लिए हटा नहीं दिया जाता है, क्योंकि उन्हें कई लेनदेन के बीच निर्भरता को ट्रैक करने की आवश्यकता होती है। लेकिन किसी भी मामले में, वे स्वचालित रूप से प्रबंधित होते हैं।

PostgreSQL में सभी अनुक्रमणिका प्रकार विधेय ताले का समर्थन नहीं करते हैं। पहले, केवल बी-पेड़ ही इस पर गर्व कर सकते थे, लेकिन पोस्टग्रेसीक्यू 11 में स्थिति में सुधार हुआ: हैश इंडेक्स, जीएसटी और जीआईएन को सूची में जोड़ा गया। यदि इंडेक्स एक्सेस का उपयोग किया जाता है, और इंडेक्स विधेय तालों के साथ काम नहीं करता है, तो पूरे सूचकांक को ताला में बंद कर दिया जाता है। बेशक, इससे झूठे लेनदेन की संख्या भी बढ़ जाती है।

अंत में, मैं ध्यान देता हूं कि यह विधेय तालों के उपयोग के साथ है कि एक प्रतिबंध है, जो पूर्ण अलगाव की गारंटी देने के लिए, सभी लेन-देन को क्रमिक स्तर पर काम करना चाहिए। यदि कोई लेन-देन एक अलग स्तर का उपयोग करता है, तो यह केवल लॉक की भविष्यवाणी नहीं करेगा (और जांच करेगा)।

परंपरा के अनुसार, मैं विधेय तालों पर README का लिंक छोड़ दूंगा, जहां से आप स्रोत कोड का अध्ययन शुरू कर सकते हैं।

जारी रखा जाए

Source: https://habr.com/ru/post/hi465263/


All Articles