2008-11-11 18 views
24

मैं लागू करने के लिए (क्या मुझे लगता है कि है) के लिए एक काउंटर के लिए एक बहुत सरल डाटा मॉडल कोशिश कर रहा हूँ:Django में परमाणु संचालन?

class VisitorDayTypeCounter(models.Model): 
    visitType = models.CharField(max_length=60) 
    visitDate = models.DateField('Visit Date') 
    counter = models.IntegerField() 

जब कोई माध्यम से आता है, यह एक पंक्ति है कि visitType और visitDate मैच के लिए दिखेगा; यदि यह पंक्ति मौजूद नहीं है, तो यह काउंटर = 0 के साथ बनाई जाएगी।

फिर हम काउंटर को बढ़ाते हैं और बचाते हैं।

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

अब तक मुझे वास्तव में Django दस्तावेज़ीकरण में या ट्यूटोरियल में एक अच्छा तरीका नहीं मिला है (वास्तव में, ऐसा लगता है कि ट्यूटोरियल की वोट हिस्से में दौड़ की स्थिति है)।

मैं इसे सुरक्षित तरीके से कैसे कर सकता हूं?

उत्तर

1

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

class VisitorDayTypeCounterManager(models.Manager): 
    def get_query_set(self): 
     qs = super(VisitorDayTypeCounterManager, self).get_query_set() 

     from django.db import connection 
     cursor = connection.cursor() 

     pk_list = qs.values_list('id', flat=True) 
     cursor.execute('UPDATE table_name SET counter = counter + 1 WHERE id IN %s', [pk_list]) 

     return qs 

class VisitorDayTypeCounter(models.Model): 
    ... 

    objects = VisitorDayTypeCounterManager() 
+0

क्या यह अभी भी संभव नहीं है कि डेटाबेस इस क्वेरी को दो अलग-अलग कनेक्शनों पर एक साथ निष्पादित करेगा और अभी भी एक (बहुत कम संभावना) दौड़ की स्थिति है?यह सब इस क्वेरी के आस-पास छिपे लेनदेन पर निर्भर करता है जो कनेक्शन परत द्वारा लगाया जाता है जो ओप परमाणु बनाता है। –

+0

यदि आप DjangoCon से "क्यों मैं नफरत है Django" मुख्य नोट देखते हैं, तो इस प्रकार की क्वेरी को एसक्यूएल में वृद्धि करने के लिए उचित, रेस कंडीशन-फ्री तरीका के रूप में दिया जाता है (यह जानकर कि Django के ORM आपके लिए यह नहीं कर सकता)। – iconoplast

+0

मैं आपकी स्लाइड देखूंगा ... आपने मेरे संदेह की बहुत पुष्टि की है कि ओआरएम इसे अपने आप नहीं करेगा। सहायता के लिए धन्यवाद! –

5

दो सुझाव:

अपने मॉडल के लिए एक unique_together जोड़ें, और अपवाद संचालक में निर्माण लपेट डुप्लिकेट को पकड़ने के लिए:

class VisitorDayTypeCounter(models.Model): 
    visitType = models.CharField(max_length=60) 
    visitDate = models.DateField('Visit Date') 
    counter = models.IntegerField() 
    class Meta: 
     unique_together = (('visitType', 'visitDate')) 

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

+0

unique_together निश्चित रूप से मुझे थोड़ा अधिक सहज महसूस करता है:

from django.db.models import F product = Product.objects.get(name='Venezuelan Beaver Cheese') product.number_sold = F('number_sold') + 1 product.save() 

अधिक जानकारी के लिए दस्तावेज़ देखें। संभावना है कि इस दौड़ को हिट करने के कारण कभी भी पर्याप्त ट्रैफिक नहीं होगा, लेकिन जब से मैं एक ही समय में Django सीख रहा हूं, मुझे लगा कि मैं इसे "सही करना" चाहता हूं। सहायता के लिए धन्यवाद! –

+0

हाँ, मैं आपको सुनता हूं। शायद यहां के आसपास कोई और व्यक्ति इसे संभालने के लिए एक ओआरएम सुविधा से अवगत होगा, या अगर इस परिदृश्य के खिलाफ कुछ अंतर्निहित सुरक्षित हैं तो साफ़ कर सकते हैं। –

1

डाटाबेस को समवर्ती परत के रूप में क्यों उपयोग नहीं करते? टाइप करने और यात्रा करने के लिए तालिका को प्राथमिक कुंजी या अद्वितीय बाधा जोड़ें। अगर मुझे गलत नहीं लगता है, तो django वास्तव में अपने डेटाबेस मॉडल वर्ग में इसका समर्थन नहीं करता है या कम से कम मैंने एक उदाहरण नहीं देखा है।

एक बार जब आप बाधा/कुंजी तालिका में जोड़ा गया है, तो तुम सब करने की है:

  1. जांच करता है, तो पंक्ति होती है। यदि यह है, तो इसे लाओ।
  2. पंक्ति डालें। अगर कोई त्रुटि नहीं है तो आप ठीक हैं और आगे बढ़ सकते हैं।
  3. यदि कोई त्रुटि है (यानी दौड़ की स्थिति), पंक्ति को फिर से प्राप्त करें। यदि कोई पंक्ति नहीं है, तो यह एक वास्तविक त्रुटि है। अन्यथा, आप ठीक हैं।

यह इस तरह से करना बुरा है, लेकिन यह पर्याप्त तेज़ी से लगता है और अधिकांश स्थितियों को कवर करेगा।

+0

यह उस मामले को संभाल नहीं करता है जहां दो लोग काउंटर को एक ही समय में अपडेट करने के लिए जाते हैं। –

0

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

Django docs. देखें एक लेनदेन मध्यम बर्तन है, या आप लेनदेन बनाने के लिए विचारों या विधियों के आसपास सजावटी का उपयोग कर सकते हैं।

+0

मैं मानता हूं कि लेन-देन यहां उत्तर की तरह लगते हैं, लेकिन यह स्पष्ट नहीं है कि कार्यक्षमता वास्तव में वृद्धि की समस्या को हल करेगी - पंक्ति प्राप्त करने के लिए चयन अभी भी सफल रहेगा, और काउंटर के मूल्य को बदलने के लिए अद्यतन अभी भी सफल होगा। अगर मैं गलत हूं, तो एक उदाहरण शानदार होगा। –

+0

आपको इसे इस तरह से करने के लिए चयन के दौरान तालिका को लॉक करने की आवश्यकता होगी, और जैसा कि सैम द्वारा उल्लिखित है, जो आपके प्रदर्शन को खींच देगा। यदि आप अक्सर काउंटर को कम नहीं करते हैं तो यह सबसे अच्छा तरीका है। –

12

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

+0

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

+0

मुझे लगता है कि यह इस बात पर निर्भर करता है कि प्रक्रिया को पढ़ने-भारी या लिखने-भारी होने जा रहा है या नहीं। गिनती को मेरे सिस्टम में बढ़ने से कहीं अधिक बार पढ़ा जा रहा है, इसलिए समस्या के अनुसार, यह सबसे अच्छी योजना नहीं हो सकती है। हालांकि, यह मेरे पास अन्य चिंताओं को हल करता है, इसलिए धन्यवाद! –

+0

इस बात पर निर्भर करते हुए कि गणना कैसे की जा सकती है, आप पृष्ठभूमि प्रक्रिया को हर बार उन्हें संक्षेप में जोड़ सकते हैं। तो आप प्रति अनुरोध एकत्रीकरण नहीं कर रहे होंगे। –

6

आप समर्थन डेटाबेस स्तर लॉकिंग के लिए http://code.djangoproject.com/ticket/2705 से पैच का उपयोग कर सकते हैं।

पैच के साथ इस कोड को परमाणु होगा:

visitors = VisitorDayTypeCounter.objects.get(day=curday).for_update() 
visitors.counter += 1 
visitors.save() 
+0

वह बहुत अच्छा है। मैंने नहीं देखा कि जब मैंने पहली बार सवाल पूछा (3 साल पहले!) –

26

Django 1.1 के रूप में आप ORM के एफ() एक्सप्रेशन का उपयोग कर सकते हैं।

https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields

https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F

+0

कूल! यह एक शानदार दृष्टिकोण है। अगर मैं इस परियोजना पर काम कर रहा था तो केवल वही था। –

+0

वास्तव में बहुत अच्छा, जानकारी के लिए धन्यवाद! –

+0

आधुनिक Django स्थापनाओं के लिए, यह सही जवाब है और ओपी द्वारा इस तरह के रूप में प्रतिबिंबित किया जाना चाहिए। – claymation

संबंधित मुद्दे