Django: मैं डेटाबेस प्रविष्टियों के समवर्ती संशोधन के खिलाफ कैसे रक्षा कर सकता हूं


81

यदि दो या दो से अधिक उपयोगकर्ताओं द्वारा एक ही डेटा बेस प्रविष्टि के समवर्ती संशोधनों से बचाने का कोई तरीका है?

यह उपयोगकर्ता को एक त्रुटि संदेश दिखाने के लिए स्वीकार्य होगा जो दूसरा कमिट / सेव ऑपरेशन कर रहा है, लेकिन डेटा को चुपचाप ओवरराइट नहीं किया जाना चाहिए।

मुझे लगता है कि प्रविष्टि को लॉक करना एक विकल्प नहीं है, क्योंकि उपयोगकर्ता "बैक" बटन का उपयोग कर सकता है या बस अपने ब्राउज़र को बंद कर सकता है, हमेशा के लिए ताला छोड़ सकता है।


4
यदि एक ऑब्जेक्ट को कई, समवर्ती उपयोगकर्ताओं द्वारा अपडेट किया जा सकता है, तो आपके पास एक बड़ा डिज़ाइन मुद्दा हो सकता है। यह उपयोगकर्ता-विशिष्ट संसाधनों के बारे में सोचने या प्रसंस्करण के चरणों को अलग करने के लिए अलग हो सकता है ताकि इसे मुद्दा न बनाया जा सके।
एस.लॉट

जवाबों:


48

इस तरह से मैं Django में आशावादी ताला लगा रहा हूं:

updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\
          .update(updated_field=new_value, version=e.version+1)
if not updated:
    raise ConcurrentModificationException()

ऊपर सूचीबद्ध कोड को कस्टम प्रबंधक में एक विधि के रूप में लागू किया जा सकता है ।

मैं निम्नलिखित धारणाएं बना रहा हूं:

  • फ़िल्टर ()। अद्यतन () एकल डेटाबेस क्वेरी में परिणाम होगा क्योंकि फ़िल्टर आलसी है
  • एक डेटाबेस क्वेरी परमाणु है

ये धारणाएं यह सुनिश्चित करने के लिए पर्याप्त हैं कि इससे पहले किसी और ने प्रविष्टि को अपडेट नहीं किया है। यदि कई पंक्तियों को इस तरह से अपडेट किया जाता है तो आपको लेनदेन का उपयोग करना चाहिए।

चेतावनी Django डॉक्टर :

ध्यान रखें कि अद्यतन () विधि सीधे SQL कथन में परिवर्तित हो जाती है। यह प्रत्यक्ष अपडेट के लिए एक बल्क ऑपरेशन है। यह आपके मॉडल पर कोई भी सेव () तरीके नहीं चलाता है, या pre_save या post_save संकेतों का उत्सर्जन करता है


12
अच्छा लगा! हालांकि 'और' के बजाय 'और' नहीं होना चाहिए, हालांकि?
गाइल्स थॉमस

1
क्या आप 'अपडेट' नहीं चल रहे सेव () मेथड्स को 'अपडाउन्ड सेव' () मेथड के अंदर 'अपडेट' करने के लिए 'कॉल' लगाकर सेविंग मेथड को रोक सकते हैं?
जोनाथन हार्टले

1
क्या होता है जब दो धागे समवर्ती कॉल करते हैं filter, दोनों को एक समान सूची मिलती है अनमॉडिफाइड e, और फिर दोनों समवर्ती कॉल update? मुझे कोई सेमीफ़ायर नहीं दिखता है जो फ़िल्टर और ब्लॉक को एक साथ अपडेट करता है। संपादित करें: ओह, मैं अब आलसी फिल्टर को समझता हूं। लेकिन अद्यतन मानने की वैधता क्या है () परमाणु है? निश्चित रूप से DB समवर्ती पहुँच को संभालता है
totowtwo

1
@totowtwo ACID में I आदेश देने की गारंटी देता है ( en.wikipedia.org/wiki/ACID )। यदि एक समवर्ती एक समवर्ती से संबंधित डेटा पर क्रियान्वित हो रहा है (लेकिन बाद में शुरू हुआ) का चयन करें जब तक कि अद्यतन नहीं किया जाता है तब तक यह ब्लॉक हो जाएगा। हालाँकि एक ही समय में कई चयन निष्पादित किए जा सकते हैं।
किट सुंडे

1
ऐसा लगता है कि केवल ऑटोकॉमिट मोड (जो डिफ़ॉल्ट है) के साथ ठीक से काम करेगा। अन्यथा अंतिम COMMIT इस अद्यतन SQL कथन से अलग हो जाएगा, इसलिए समवर्ती कोड उनके बीच चल सकता है। और हमारे पास Django में ReadCommited आइसोलेशन स्तर है, इसलिए यह पुराना संस्करण पढ़ेगा। (मैं यहां मैनुअल लेनदेन क्यों चाहता हूं - क्योंकि मैं इस अपडेट के साथ एक अन्य तालिका में एक पंक्ति बनाना चाहता हूं।) महान विचार, हालांकि।
एलेक्स लोकक

39

यह प्रश्न थोड़ा पुराना है और मेरा उत्तर थोड़ा देर से आया है, लेकिन मुझे जो समझ में आया है, उसका उपयोग करके Django 1.4 में तय किया गया है :

select_for_update(nowait=True)

डॉक्स देखें

समर्थित डेटाबेस पर चयनित SQL कथन के लिए एक क्वेरी ... लेन-देन के अंत तक पंक्तियों को लॉक कर देगा, जो लौटाता है।

आमतौर पर, यदि किसी अन्य लेनदेन ने पहले से ही चयनित पंक्तियों में से एक पर ताला लगा दिया है, तो ताला बंद होने तक क्वेरी अवरुद्ध हो जाएगी। यदि यह वह व्यवहार नहीं है जिसे आप चाहते हैं, तो select_for_update (nowait = True) पर कॉल करें। यह कॉल को गैर-अवरुद्ध कर देगा। यदि कोई परस्पर विरोधी लॉक किसी अन्य लेन-देन द्वारा पहले ही अधिग्रहित कर लिया गया है, तो क्वेरीसेट का मूल्यांकन होने पर, डेटाबेसआयर्स उठाया जाएगा।

बेशक यह केवल तभी काम करेगा जब बैक-एंड "सिलेक्ट फॉर अपडेट" फीचर का समर्थन करे, जो उदाहरण के लिए साइक्लाइट नहीं करता है। दुर्भाग्य से: nowait=TrueMySql द्वारा समर्थित नहीं है, वहां आपको उपयोग करना होगा:, nowait=Falseजो केवल तब तक ब्लॉक रहेगा जब तक कि लॉक जारी न हो जाए।


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

@ giZm0 यह अभी भी निराशावादी ताला लगाता है। पहला धागा जो ताला प्राप्त करता है, उसे अनिश्चित काल तक पकड़ सकता है।
knaperek

6
मुझे यह जवाब पसंद है क्योंकि यह Django के दस्तावेज का है न कि किसी तीसरे पक्ष का सुंदर आविष्कार।
ऐजोमेक

29

वास्तव में, लेन-देन आपको यहां बहुत मदद नहीं करते हैं ... जब तक कि आप कई HTTP अनुरोधों पर लेन-देन नहीं करना चाहते हैं (जो कि आप शायद नहीं चाहते हैं)।

हम आमतौर पर उन मामलों में जो उपयोग करते हैं वह "ऑप्टिमिस्टिक लॉकिंग" है। Django ORM का समर्थन नहीं करता है, जहां तक ​​मुझे पता है। लेकिन इस फीचर को जोड़ने के बारे में कुछ चर्चा हुई है।

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

  1. डेटा पढ़ें और उपयोगकर्ता को दिखाएं
  2. उपयोगकर्ता डेटा को संशोधित करता है
  3. उपयोगकर्ता डेटा पोस्ट करते हैं
  4. एप्लिकेशन डेटाबेस में इसे वापस बचाता है।

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

आप ऐसा कुछ के साथ एकल SQL कॉल के साथ कर सकते हैं:

UPDATE ... WHERE version = 'version_from_user';

यह कॉल डेटाबेस को केवल तभी अपडेट करेगा जब संस्करण अभी भी वैसा ही हो।


1
यही सवाल स्लैशडॉट पर भी दिखाई दिया। आशावादी ताला लगा आप का सुझाव भी वहाँ प्रस्तावित किया गया था, लेकिन बेहतर imho थोड़ा विस्तार से बताया: hardware.slashdot.org/comments.pl?sid=1381511&cid=29536367
hopla

5
यह भी ध्यान दें कि आप इस स्थिति से बचने के लिए लेन-देन का उपयोग करना चाहते हैं: इस स्थिति से बचने के लिए: hardware.slashdot.org/comments.pl?sid=1381511&cid=29536613 Django डेटाबेस में किसी भी गतिविधि पर स्वचालित रूप से अपनी कार्रवाई को शुरू करने के लिए मिडलवेयर प्रदान करता है। प्रारंभिक अनुरोध से और केवल एक succesfull प्रतिक्रिया के बाद कमिट करना : docs.djangoproject.com/en/dev/topics/db/transactions (आप पर ध्यान दें: लेन-देन मिडलवेयर केवल अनुकूलन योग्य लॉकिंग के साथ उपरोक्त समस्या से बचने में मदद करता है, यह लॉकिंग प्रदान नहीं करता है अपने आप में)
हॉप

मैं यह भी करने के लिए विवरण की तलाश कर रहा हूं कि यह कैसे करना है। अब तक कोई भाग्य नहीं।
सीनयबॉय

1
आप django बल्क अपडेट का उपयोग करके ऐसा कर सकते हैं। मेरा जवाब जांचें।
आंद्रेई सावू

14

Django 1.11 में आपके व्यापार तर्क आवश्यकताओं के आधार पर इस स्थिति को संभालने के लिए तीन सुविधाजनक विकल्प हैं:

  • Something.objects.select_for_update() तब तक ब्लॉक रहेगा जब तक मॉडल मुक्त नहीं हो जाता
  • Something.objects.select_for_update(nowait=True)और पकड़ें DatabaseErrorकि मॉडल वर्तमान में अद्यतन के लिए बंद है
  • Something.objects.select_for_update(skip_locked=True) उन वस्तुओं को वापस नहीं करेगा जो वर्तमान में बंद हैं

मेरे आवेदन में, जिसमें विभिन्न मॉडलों पर इंटरैक्टिव और बैच वर्कफ़्लो दोनों हैं, मैंने अपने अधिकांश समवर्ती प्रसंस्करण परिदृश्यों को हल करने के लिए इन तीन विकल्पों को पाया।

select_for_updateअनुक्रमिक बैच प्रक्रियाओं में "प्रतीक्षा" बहुत सुविधाजनक है - मैं चाहता हूं कि वे सभी निष्पादित करें, लेकिन उन्हें अपना समय लेने दें। इसका nowaitउपयोग तब किया जाता है जब कोई उपयोगकर्ता किसी ऐसी वस्तु को संशोधित करना चाहता है जो वर्तमान में अद्यतन के लिए बंद है - मैं सिर्फ उन्हें बताऊंगा कि इस समय इसे संशोधित किया जा रहा है।

skip_lockedअद्यतन का एक और प्रकार, जब उपयोगकर्ता एक वस्तु का एक रीस्कैन ट्रिगर कर सकते हैं के लिए उपयोगी है - और मुझे परवाह नहीं है जो इसे चलाता है, जब तक यह शुरू हो रहा है, इसलिए skip_lockedमुझे चुपचाप डुप्लिकेट चलाता को छोड़ कर सकते हैं।


1
क्या मुझे लेनदेन के लिए अपडेट का चयन करने की आवश्यकता है। यदि वास्तव में अपडेट के लिए परिणामों का उपयोग कर रहे हैं? क्या यह पूरी तालिका को लॉक नहीं करता है जिससे select_for_update a noop हो जाता है?
पॉल केंजोरा

3

भविष्य के संदर्भ के लिए, https://github.com/RobCombs/django-locking देखें । यह एक तरह से लॉकिंग करता है, जो उपयोगकर्ता के पेज को छोड़ने पर जावास्क्रिप्ट अनलॉकिंग के मिश्रण से, और लॉक टाइमआउट (जैसे उपयोगकर्ता का ब्राउज़र क्रैश होने पर) लॉकिंग को कभी नहीं छोड़ता है। प्रलेखन बहुत पूर्ण है।


3
मैं, यह एक बहुत ही अजीब विचार है।
जुल्क्स

1

आपको शायद कम से कम इस समस्या की परवाह किए बिना django लेनदेन मिडिलवेयर का उपयोग करना चाहिए।

एकाधिक उपयोगकर्ताओं को एक ही डेटा संपादित करने की आपकी वास्तविक समस्या के रूप में ... हाँ, लॉकिंग का उपयोग करें। या:

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

बहुत संभव है कि एक django ऐप / लाइब्रेरी आपके लिए यह सबसे अधिक हो।


यह भी आशावादी लॉकिंग है, जैसे गिलौम प्रस्तावित है। लेकिन वह सभी बिंदुओं को प्राप्त करने के लिए लग रहा था :)
हॉप

0

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


मैं एक लेन-देन या कई अनुरोधों पर ताला नहीं लगाना चाहता, क्योंकि इसमें किसी भी समय लग सकता है (और कभी भी समाप्त नहीं हो सकता है)
Ber

यदि कोई लेन-देन शुरू होता है, तो इसे समाप्त करना होगा। उपयोगकर्ता द्वारा "सबमिट" पर क्लिक करने के बाद आपको केवल रिकॉर्ड को लॉक करना चाहिए (या लेनदेन शुरू करना, या जो भी आप करना चाहते हैं), न कि जब वे रिकॉर्ड को देखने के लिए खोलते हैं।
हार्ले होल्कोम्ब

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

आप सही हैं, लेकिन समस्या यह है कि इसका कोई हल नहीं है। एक उपयोगकर्ता जीत जाता है, दूसरे को एक असफल संदेश मिलता है। बाद में आप रिकॉर्ड कम समस्याओं को बंद कर देंगे।
हार्ले होल्कोम्बे

मैं सहमत हूँ। मैं अन्य उपयोगकर्ता के लिए विफल संदेश को पूरी तरह से स्वीकार करता हूं। मैं इस मामले का पता लगाने के लिए एक अच्छा तरीका ढूंढ रहा हूं (जो मुझे बहुत कम होने की उम्मीद है)।
बेर

0

ऊपर का विचार

updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\
      .update(updated_field=new_value, version=e.version+1)
if not updated:
      raise ConcurrentModificationException()

बहुत अच्छा लग रहा है और बिना सीरियल लेनदेन के भी ठीक काम करना चाहिए।

समस्या यह है कि कैसे बहरेपन .save () व्यवहार को बढ़ाने के लिए है।

मैंने कस्टम मैनेजर के विचार को देखा।

मेरी योजना प्रबंधक _update पद्धति को ओवरराइड करने की है जिसे अद्यतन करने के लिए Model.save_base () द्वारा कहा जाता है।

यह Django 1.3 में वर्तमान कोड है

def _update(self, values, **kwargs):
   return self.get_query_set()._update(values, **kwargs)

आईएमएचओ की जरूरत कुछ इस तरह है:

def _update(self, values, **kwargs):
   #TODO Get version field value
   v = self.get_version_field_value(values[0])
   return self.get_query_set().filter(Q(version=v))._update(values, **kwargs)

इसी तरह की बात डिलीट पर होनी चाहिए। हालाँकि डिलीट करना थोड़ा और मुश्किल है क्योंकि Django इस क्षेत्र में django.db.models.deletion.Collector के माध्यम से काफी कुछ वूडू लागू कर रहा है।

यह अजीब है कि Django जैसे मामूली उपकरण में ऑप्टिक्टिक कॉन्सुरेंसी कंट्रोल के लिए मार्गदर्शन का अभाव है।

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


-2

सुरक्षित होने के लिए डेटाबेस को लेनदेन का समर्थन करना होगा ।

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

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

मुझे django नहीं पता है, इसलिए मैं आपको th कोड 3s नहीं दे सकता ..;)


-6

यहाँ से: किसी वस्तु को ओवरराइट करने से
कैसे रोका जाए किसी और ने संशोधित किया है

मैं मान रहा हूं कि टाइमस्टैम्प को एक छिपे हुए क्षेत्र के रूप में आयोजित किया जाएगा, जिसके विवरण को आप सहेजने की कोशिश कर रहे हैं।

def save(self):
    if(self.id):
        foo = Foo.objects.get(pk=self.id)
        if(foo.timestamp > self.timestamp):
            raise Exception, "trying to save outdated Foo" 
    super(Foo, self).save()

1
कोड टूट गया है। दौड़ और क्वेरी को बचाने के बीच दौड़ की स्थिति अभी भी हो सकती है। आपको ऑब्जेक्ट का उपयोग करने की आवश्यकता है। फ़िल्टर (आईडी = .. & टाइमस्टैम्प चेक) .update (...) और एक अपवाद बढ़ाएं यदि कोई पंक्ति अपडेट नहीं हुई थी।
आंद्रेई सावु
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.