Django orm का उपयोग करते समय
महत्वपूर्ण प्रदर्शन हानि का सामना करना पड़ा, मैंने orm का उपयोग करने के विभिन्न तरीकों पर विचार करते हुए एक रास्ता खोजना शुरू कर दिया। मैंने क्या किया - रोल देखें।
Django orm का उपयोग करके कोड का एक नियमित टुकड़ा कैसे लिखा जाता है?एक नियम के रूप में, यह टुकड़ा एक निश्चित फ़ंक्शन में प्रवेश करता है, ठीक है, उदाहरण के लिए, दृश्य, पैरामीटर प्राप्त करता है और इन मापदंडों के आधार पर परिणाम बनाता है।
एक उदाहरण के रूप में, निम्नलिखित मूल स्थिति पर विचार करें: हम उन समूहों के नामों की सूची प्राप्त करना चाहते हैं जो वर्तमान उपयोगकर्ता के हैं। सबसे सरल और सबसे स्पष्ट तरीका जो पहली बार दिमाग में आता है, वह है रिश्ते के माध्यम से समूहों की एक सूची प्राप्त करना और उनके नामों का पता लगाना:
def myview(request): u = request.user a = [g.name for g in u.groups.all()] ...
हम यह जांचेंगे कि इस टुकड़े का प्रदर्शन क्या होगा, इस बात को ध्यान में रखते हुए कि उपयोगकर्ता ऑब्जेक्ट अनुरोध .user पहले से ही अनुरोध के प्रारंभिक प्रसंस्करण के चरण में प्राप्त किया गया है।
थॉट समूह बनाएं और इसमें पहले उपयोगकर्ता के साथ जुड़ें:
>>> u = User.objects.all()[0] >>> g = Group(name='thetest') >>> g.save() >>> u.groups.add(g) >>> u.groups.all() [<Group: thetest>]
मैं भविष्य में सभी परीक्षणों में इस मामले का उपयोग करूंगा। चूंकि सब कुछ शेल के माध्यम से किया जाता है, इसलिए मैं इस स्तर पर प्राप्त चर यू का उपयोग उनमें भी करता हूं।तो, नंबर 1 का परीक्षण करें, हम कोड के गर्भित टुकड़े को निष्पादित करते हैं। जांचें कि क्या यह वास्तव में खोज सूची लौटाता है:
>>> a = [g.name for g in u.groups.all()] >>> a [u'thetest']
प्रदर्शन को मापने के लिए, इसे 1000 बार चलाएं।
>>> def test1(): ... import datetime ... t1 = datetime.datetime.now() ... for i in xrange(1000): ... a = [g.name for g in u.groups.all()] ... t2 = datetime.datetime.now() ... print "%s" % (t2 - t1) ... >>> test1() 0:00:01.437324
हमारे चक्र के एक हजार चक्कर लगाने में लगभग डेढ़ सेकंड का समय लगा, जो प्रति अनुरोध 1.5 मिलीसेकंड देता है।
अनुभवी जंगल के लेखक शायद पहले से ही मेरी नाक को इस तथ्य पर प्रहार करने में जुट गए हैं कि यह टुकड़ा इष्टतमता से दूर है। वास्तव में, आप पहली नज़र में कोड का एक अधिक इष्टतम टुकड़ा देख सकते हैं जो एक समूह ऑब्जेक्ट का निर्माण किए बिना समान कार्य करता है और डेटाबेस से केवल उस डेटा को प्राप्त करता है जिसकी हमें वास्तव में आवश्यकता है:
>>> a = [g['name'] for g in u.groups.values('name')] >>> a [u'thetest']
खैर, इस टुकड़े को मापें।
>>> def test2(): ... import datetime ... t1 = datetime.datetime.now() ... for i in xrange(1000): ... a = [g['name'] for g in u.groups.values('name')] ... t2 = datetime.datetime.now() ... print "%s" % (t2 - t1) ... >>> test2() 0:00:01.752529
यह अप्राकृतिक लगता है, लेकिन क्या हमारे कोड का दूसरा संस्करण पहले की तुलना में कम इष्टतम है?
वास्तव में, यह तरीका है। कॉलिंग वैल्यू पर नुकसान () और अनुरोध का अतिरिक्त विश्लेषण समूह ऑब्जेक्ट के निर्माण पर संभावित बचत से अधिक हो गया है और इसके सभी क्षेत्रों के मूल्यों को प्राप्त कर रहा है।
लेकिन मुझे माफ करना? और क्यों, वास्तव में, प्रत्येक बार जब हम अनुरोध
का पुन: डिजाइन और विश्लेषण करते हैं, अगर हमारे विचार में हम हमेशा
एक ही अनुरोध को निष्पादित
करते हैं , और केवल उस उपयोगकर्ता ऑब्जेक्ट जिस पर यह अनुरोध निष्पादित होता है, अलग-अलग होगा?
दुर्भाग्य से, django शुरू में आपको अग्रिम में अनुरोध तैयार
करने की अनुमति नहीं देता है , आवश्यक रूप से तैयार अनुरोध का संदर्भ देते हुए। कोई संबंधित कॉल नहीं है, और क्वेरी पीढ़ी सिंटैक्स का अर्थ केवल क्वेरी पैरामीटर के रूप में विशिष्ट मानों का उपयोग है।
हमें स्रोत पर थोड़ा सा चढ़ना होगा। मैं django_extensions के डेवलपर्स और उनकी अद्भुत शेल_प्लस टीम को धन्यवाद देने के लिए यह अवसर लेना चाहता हूं, जो वस्तुओं के आत्मनिरीक्षण की सुविधा प्रदान करता है।
यह पता चलता है कि QuerySet ऑब्जेक्ट (यह वह है जो ऑब्जेक्ट एक्सेस करने के समय उदाहरण के लिए प्राप्त किया जाता है ।all ()) में एक क्वेरी प्रॉपर्टी होती है, django.db.models.sql.query.Query क्लास का ऑब्जेक्ट। जिसके बदले में एक sql_with_params () विधि है
यह विधि पैरामीटर का एक सेट लौटाती है जो कर्सर.execute () - यानी SQL अभिव्यक्ति स्ट्रिंग और अतिरिक्त मापदंडों को पास करने के लिए पूरी तरह से तैयार है। बड़ी बात यह है कि ये बहुत ही उन्नत पैरामीटर ऐसे पैरामीटर हैं जो इसे बनने पर QuerySet को दिए जाते हैं:
>>> u.groups.all().values('name').query.sql_with_params() ('SELECT `auth_group`.`name` FROM `auth_group` INNER JOIN `auth_user_groups` ON (`auth_group`.`id` = `auth_user_groups`.`group_id`) WHERE `auth_user_groups`.`user_id` = %s ', (1,))
अब, यदि हमें एक तैयार SQL क्वेरी मिलती है और इसमें विभिन्न पैरामीटर मानों को प्रतिस्थापित किया जाता है, तो हम क्वेरी तैयार करने पर संसाधनों को बर्बाद किए बिना क्वेरी को निष्पादित कर सकते हैं।
ऐसा करने के लिए, एक विशेष वर्ग बनाएं जो हैक के सभी विवरणों के अंदर छुपाता है जिसे हम करने जा रहे हैं।
from django.db import connection from django.db.models.query import QuerySet,ValuesQuerySet import django from threading import local class PQuery(local): def __init__(self,query,connection=connection,**placeholders): self.query = query self.connection = connection self.placeholders = placeholders self.replaces = {} sql = None try: sql = self.query.query.sql_with_params()
UPD: 2012-08-06 19:20:00 MSK - जटिल प्रश्नों का प्रदर्शन करते समय और बहुउद्देशीयता में सुधार करते हुए, मल्टीट्रेडिंग के साथ संगतता के बारे में कोड में सुधार किए गए।
पिछला कोड संस्करण from django.db import connection from django.db.models.query import QuerySet,ValuesQuerySet class ParametrizedQuery: def __init__(self,query,connection=connection,**placeholders): self.query = query self.connection = connection self.placeholders = placeholders self.replaces = {} sql = self.query.query.sql_with_params() self.places = list(sql[1]) self.sql = sql[0] self.is_values = isinstance(query,ValuesQuerySet) self.cursor = None for p in self.placeholders: v = self.placeholders[p] self.replaces[p] = self.places.index(v) def execute(self,**kw): for k in kw: self.places[self.replaces[k]] = kw[k] if not self.cursor: self.cursor = self.connection.cursor() self.cursor.execute(self.sql,self.places) if not hasattr(self,'fldnms'): self.fldnms = [col[0] for col in self.cursor.description] if self.is_values: return [dict(zip(self.fldnms,row)) for row in self.cursor.fetchall()] return [self.query.model(**dict(zip(self.fldnms,row))) for row in self.cursor.fetchall()]
यह वर्ग क्या करता है? वह एक अनुरोध प्राप्त करता है और इससे तैयार SQL और मापदंडों को चुनता है। हम एक ऐसा अनुरोध बना सकते हैं जिसमें प्रत्येक पैरामीटर जिसे हम स्थानापन्न करने जा रहे हैं, हमारे पास पहले से ज्ञात एक विशेष मूल्य है। हम इन मूल्यों का उपयोग उस स्थान की खोज के लिए करेंगे जहां हम निष्पादन के दौरान पारित मूल्यों को स्थानापन्न करना चाहते हैं।
कुछ अतिरिक्त कार्यान्वयन विवरण भी हमें संसाधनों को बचाने में मदद करेंगे।
- अनुरोध की पहली निष्पादन के दौरान प्राप्त फ़ील्ड नामों में फ़ील्ड की एक सरणी होती है। बाद के कॉल तैयार सरणी का उपयोग करेंगे।
- प्रॉपर्टीज़ में पैरामीटर नंबरों को प्रतिस्थापन नामों की मैपिंग शामिल है।
- हमारी कक्षा का प्रत्येक ऑब्जेक्ट अपना स्वयं का कर्सर रखेगा। इस चरण से संभावित त्वरण का परिणाम है, पहले, कर्सर का निर्माण एक महंगा ऑपरेशन है, और दूसरी बात, pyodbc विवरण से निम्नलिखित वाक्यांश का है, जिसे डेटाबेस के बैकएंड के रूप में इस्तेमाल किया जा सकता है: "यह अधिक कुशल भी है यदि आप अलग-अलग मापदंडों के साथ एक ही SQL को बार-बार निष्पादित करें। SQL केवल एक बार तैयार किया जाएगा। (pyodbc केवल अंतिम विवरण तैयार करता है, इसलिए यदि आप कथनों के बीच स्विच करते हैं, तो प्रत्येक को कई बार तैयार किया जाएगा।)
- Is_values प्रॉपर्टी हमें यह निर्धारित करने में मदद करेगी कि एक क्वेरी को एक मॉडल ऑब्जेक्ट वापस नहीं करना चाहिए, जो परिणाम लौटते समय ऐसी ऑब्जेक्ट बनाने पर बचाएगा।
हम मूल अनुरोध को थोड़ा संशोधित करेंगे ताकि उसमें क्रमपरिवर्तन को पर्ची करना सुविधाजनक हो:
>>> q = Group.objects.filter(user__id=12345).values('name') >>> q.query.sql_with_params() ('SELECT `auth_group`.`name` FROM `auth_group` INNER JOIN `auth_user_groups` ON (`auth_group`.`id` = `auth_user_groups`.`group_id`) WHERE `auth_user_groups`.`user_id` = %s ', (12345,))
हम प्रतिस्थापन के रूप में मूल्य 12345 का उपयोग करते हैं:
>>> p = ParametrizedQuery(q,user_id=12345) >>> [g['name'] for g in p.execute(user_id=u.id)] [u'thetest']
जब p.execute () अनुरोध निष्पादित किया गया था, तो उपयोगकर्ता पहचानकर्ता का वास्तविक मूल्य 12345 स्थानापन्न स्थान के लिए प्रतिस्थापित किया गया था।
अब देखते हैं कि कोड का प्रदर्शन कैसे बदलेगा:
>>> def test3(): ... import datetime ... t1 = datetime.datetime.now() ... for i in xrange(1000): ... a = [g['name'] for g in p.execute(user_id=u.id)] ... t2 = datetime.datetime.now() ... print "%s" % (t2 - t1) ... >>> test3() 0:00:00.217270
यह परिणाम है!
क्वेरी निष्पादन का समय 7 गुना कम हो गया था ।
वास्तविक कोड में इसका उपयोग कैसे करें?
सबसे पहले, आपको एक जगह की आवश्यकता है जिसमें एक तैयार अनुरोध संग्रहीत किया जा सकता है। दूसरे, कुछ बिंदु पर, आपको इस चर को भरने की आवश्यकता है। उदाहरण के लिए, फ़ंक्शन कोड के पहले निष्पादन के समय। और तीसरा, निश्चित रूप से, हम क्वेरी को सीधे निष्पादित करने के बजाय एक पैरामीटर को क्वेरी के लिए उपयोग करते हैं।
def myview(request): if not hasattr(myview,'query'): myview.query = ParametrizedQuery(Group.objects.filter(user__id=12345).values('name'),user_id=12345) a = [g['name'] for g in myview.query.execute(user_id=request.user.id)] ...
सभी कोड को निष्पादित किया गया था:
- django.VERSION = (1, 4, 0, 'अंतिम', 0)
- mysql DBMS (django.db.backends.mysql)
- टेबल इंजन = MYISAM
- लोकलहोस्ट के माध्यम से कनेक्शन
- अजगर 2.7.2+ (डिफ़ॉल्ट, 4 अक्टूबर 2011, 20:03:08) linux2 पर [जीसीसी 4.6.1]
- लिनक्स होस्ट सेवा 3.0.0-22-जेनेरिक # 36-उबंटू एसएमपी मंगल 12 जून 17:13:04 यूटीसी 2012 i686 एथलॉन i386 GNU / लिनक्स
विशेषज्ञों से टिप्पणियां स्वागत योग्य हैं।