Django ORM में select_related और prefetch_related के बीच क्या अंतर है?


291

Django में डॉक्टर,

select_related() विदेशी-कुंजी रिश्तों का "अनुसरण" करता है, जब वह अपनी क्वेरी को निष्पादित करता है तो अतिरिक्त संबंधित वस्तु डेटा का चयन करता है।

prefetch_related() प्रत्येक रिश्ते के लिए एक अलग खोज करता है, और पायथन में "जॉइनिंग" करता है।

"अजगर में शामिल होने" का क्या अर्थ है? क्या कोई उदाहरण के साथ वर्णन कर सकता है?

मेरी समझ यह है कि विदेशी कुंजी संबंध के लिए, उपयोग करें select_related; और M2M संबंध के लिए, का उपयोग करें prefetch_related। क्या ये सही है?


2
अजगर में शामिल होने का मतलब है कि डेटाबेस में शामिल नहीं होगा। एक select_related के साथ, आपका जुड़ाव डेटाबेस में होता है और आप केवल एक डेटाबेस क्वेरी को भुगतते हैं। Prefetch_related के साथ, आप दो प्रश्नों का निष्पादन करेंगे और फिर परिणाम ORM द्वारा 'ज्वाइन' कर दिए जाएंगे ताकि आप अभी भी object.related_set
Mark Galloway

3
एक फुटनोट के रूप में, टिम्मी ओ'मोनी डेटाबेस हिट का उपयोग करके अपने मतभेदों को भी समझा सकते हैं: लिंक
Mærcos

जवाबों:


424

आपकी समझ ज्यादातर सही है। आप select_relatedतब उपयोग करते हैं जब आप जिस वस्तु का चयन करने जा रहे हैं वह एकल वस्तु है, इसलिए OneToOneFieldया ए ForeignKey। आप का उपयोग prefetch_relatedजब आप एक चीजों की "सेट" पाने के लिए जा रहे हैं, तो ManyToManyFieldएस के रूप में आप ने कहा या रिवर्स ForeignKeyरों। बस स्पष्ट करने के लिए कि "रिवर्स ForeignKeyएस" से मेरा क्या मतलब है यहां एक उदाहरण है:

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

अंतर यह है कि select_relatedएक SQL जुड़ता है और इसलिए परिणाम SQL सर्वर से तालिका के भाग के रूप में वापस मिलता है। prefetch_relatedदूसरी ओर एक और क्वेरी निष्पादित करता है और इसलिए मूल ऑब्जेक्ट ( ModelAउपरोक्त उदाहरण में) में अनावश्यक कॉलम को कम करता है । आप किसी prefetch_relatedभी चीज के लिए उपयोग कर सकते हैं जिसका आप उपयोग कर सकते हैं select_related

ट्रेडऑफ़्स हैं जिन्हें prefetch_relatedसर्वर पर वापस चुनने के लिए आईडी की एक सूची तैयार करना और भेजना है, इसमें कुछ समय लग सकता है। मुझे यकीन नहीं है कि लेनदेन में ऐसा करने का एक अच्छा तरीका है, लेकिन मेरी समझ यह है कि Django हमेशा एक सूची भेजता है और कहता है कि SELECT ... WHk pk IN (..., ..., ...) मूल रूप से। इस मामले में यदि पूर्वनिर्मित डेटा विरल है (मान लें कि यूएस स्टेट ऑब्जेक्ट्स लोगों के पतों से जुड़ा हुआ है) तो यह बहुत अच्छा हो सकता है, हालांकि अगर यह एक-से-एक के करीब है, तो इससे बहुत सारे संचार बर्बाद हो सकते हैं। यदि संदेह है, तो दोनों का प्रयास करें और देखें कि कौन सा बेहतर प्रदर्शन करता है।

ऊपर चर्चा की गई सब कुछ मूल रूप से डेटाबेस के साथ संचार के बारे में है। हालांकि पायथन पक्ष prefetch_relatedमें अतिरिक्त लाभ है कि डेटाबेस में प्रत्येक वस्तु का प्रतिनिधित्व करने के लिए एक एकल वस्तु का उपयोग किया जाता है। साथ select_relatedनकली वस्तुओं प्रत्येक "जनक" वस्तु के लिए अजगर में बनाया जाएगा। चूंकि पायथन में वस्तुओं में स्मृति ओवरहेड का एक सभ्य बिट है, इसलिए यह एक विचार भी हो सकता है।


3
हालांकि तेज क्या है?
इलाद सिल्वर

24
select_relatedएक क्वेरी है जबकि prefetch_relatedदो है, इसलिए पूर्व तेज है। लेकिन select_relatedManyToManyField
bhinesley

31
@eladsilver धीमे उत्तर के लिए क्षमा करें। यह वास्तव में निर्भर करता है। select_relatedSQL में एक JOIN का उपयोग करता है, जबकि prefetch_relatedपहले मॉडल पर क्वेरी को चलाता है, सभी उन आईडी को इकट्ठा करता है जिन्हें इसे प्रीफ़ेट करने की आवश्यकता होती है और फिर सभी आईडी के साथ WHERE में IN क्लॉज़ के साथ एक क्वेरी चलाता है जिसकी उसे आवश्यकता होती है। यदि आप एक ही विदेशी कुंजी का उपयोग करके 3-5 मॉडल कहते हैं, select_relatedतो लगभग निश्चित रूप से बेहतर होगा। यदि आपके पास एक ही विदेशी कुंजी का उपयोग करने वाले 100 से अधिक उच्च मॉडल हैं, prefetch_relatedतो वास्तव में बेहतर हो सकता है। बीच में आपको परीक्षण करना होगा और देखना होगा कि क्या होता है।
क्रेजीकैस्टा

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

1
@GordonWrigley हाँ, जब से मैंने लिखा है कि कुछ समय हो गया है, इसलिए मैं वापस गया और थोड़ा स्पष्ट किया। मुझे यकीन नहीं है कि मैं "DB पर कम मेमोरी का उपयोग करता है" बिट से सहमत हूं, लेकिन हर चीज के लिए हां। और यह निश्चित रूप से पायथन की तरफ कम मेमोरी का उपयोग कर सकता है।
क्रेजीकैस्टा

26

दोनों विधियाँ एक ही उद्देश्य को प्राप्त करती हैं, अनावश्यक डीबी प्रश्नों को सामने लाने के लिए। लेकिन वे दक्षता के लिए विभिन्न तरीकों का उपयोग करते हैं।

इन विधियों में से किसी एक का उपयोग करने का एकमात्र कारण तब होता है जब एक बड़ी क्वेरी कई छोटे प्रश्नों के लिए बेहतर होती है। Django डेटाबेस के खिलाफ मांग प्रश्नों पर प्रदर्शन करने के बजाय स्मृति में मॉडल बनाने के लिए बड़ी क्वेरी का उपयोग करता है।

select_relatedप्रत्येक लुकअप के साथ एक जुड़ाव करता है, लेकिन सभी सम्मिलित तालिकाओं के कॉलम को शामिल करने के लिए चयन का विस्तार करता है। हालांकि इस दृष्टिकोण में एक चेतावनी है।

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

"अजगर में शामिल होने के" के लिए prefetch_relatedथोड़ा और अधिक खतरनाक तो यह होना चाहिए। यह प्रत्येक तालिका में शामिल होने के लिए एक अलग क्वेरी बनाता है। यह इनमें से प्रत्येक तालिका को WHERE IN क्लॉज़ के साथ फ़िल्टर करता है, जैसे:

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

संभावित रूप से बहुत सी पंक्तियों के साथ एक एकल प्रदर्शन करने के बजाय, प्रत्येक तालिका एक अलग क्वेरी में विभाजित है।


1

पहले से ही जवाब के माध्यम से चला गया। बस मैंने सोचा कि अगर मैं वास्तविक उदाहरण के साथ उत्तर जोड़ूं तो बेहतर होगा।

मान लीजिए कि आपके पास 3 Django मॉडल हैं जो संबंधित हैं।

class M1(models.Model):
    name = models.CharField(max_length=10)

class M2(models.Model):
    name = models.CharField(max_length=10)
    select_relation = models.ForeignKey(M1, on_delete=models.CASCADE)
    prefetch_relation = models.ManyToManyField(to='M3')

class M3(models.Model):
    name = models.CharField(max_length=10)

यहाँ आप क्वेरी कर सकता है M2मॉडल और उसके रिश्तेदार M1वस्तुओं का उपयोग कर select_relationक्षेत्र और M3का उपयोग कर वस्तुओं prefetch_relationक्षेत्र।

हालाँकि जैसा कि हमने उल्लेख किया है कि यह एक M1से M2है ForeignKey, यह किसी भी वस्तु के लिए केवल 1 रिकॉर्ड देता है M2। एक ही बात के लिए भी लागू होता है OneToOneField

लेकिन इससे वह M3संबंध M2है ManyToManyFieldजो किसी भी संख्या में M1वस्तुओं को वापस कर सकता है ।

ऐसे मामले पर विचार करें जहां आपके पास 2 M2ऑब्जेक्ट हैं m21, m22जिनके पास ID के साथ समान 5 संबद्ध M3ऑब्जेक्ट हैं 1,2,3,4,5। जब आप M3उन M2वस्तुओं में से प्रत्येक के लिए संबद्ध ऑब्जेक्ट लाते हैं, यदि आप संबंधित से संबंधित का उपयोग करते हैं, तो यह इस तरह से काम करने वाला है।

कदम:

  1. m21वस्तु खोजो ।
  2. M3ऑब्जेक्ट से संबंधित सभी ऑब्जेक्ट को क्वेरी करें m21जिनकी आईडी हैं 1,2,3,4,5
  3. m22ऑब्जेक्ट और अन्य सभी M2वस्तुओं के लिए एक ही बात दोहराएं ।

जैसा कि हम 1,2,3,4,5दोनों के लिए समान आईडी हैं m21, m22ऑब्जेक्ट, यदि हम select_related विकल्प का उपयोग करते हैं, तो यह उसी आईडी के लिए दो बार DB को क्वेरी करने जा रहा है जो पहले से ही प्रचलित थे।

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

इस तरह आप सभी M3वस्तुओं को केवल एक बार क्वेरी कर रहे हैं जो प्रदर्शन को बेहतर बनाता है।


0

जैसा कि Django प्रलेखन कहता है:

prefetch_related ()

एक QuerySet देता है जो स्वचालित रूप से, एक एकल बैच में, प्रत्येक निर्दिष्ट लुकअप के लिए संबंधित ऑब्जेक्ट्स को पुनः प्राप्त करेगा।

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

Select_related कार्य SQL से बनाकर और सेलेक्ट स्टेटमेंट में संबंधित ऑब्जेक्ट के फील्ड सहित। इस कारण से, select_related को समान डेटाबेस क्वेरी में संबंधित ऑब्जेक्ट मिलते हैं। हालाँकि, बहुत बड़े परिणाम सेट से बचने के लिए जो 'कई' संबंधों में शामिल होने से परिणाम देगा, select_related एकल-मूल्यवान रिश्तों तक सीमित है - विदेशी कुंजी और वन-टू-वन।

दूसरी ओर, प्रीफ़ैच_रेल्ट, प्रत्येक रिश्ते के लिए एक अलग लुकअप करता है, और पायथन में 'जॉइनिंग' करता है। यह कई-से-कई और कई-से-एक ऑब्जेक्ट को प्रीफ़ैच करने की अनुमति देता है, जो कि select_related का उपयोग करके नहीं किया जा सकता है, इसके अलावा विदेशी कुंजी और एक-से-एक रिश्ते जो select_related द्वारा समर्थित हैं। यह GenericRelation और GenericForeignKey के प्रीफ़ेटिंग का भी समर्थन करता है, हालाँकि, इसे परिणामों के एक सजातीय सेट तक सीमित रखा जाना चाहिए। उदाहरण के लिए, एक GenericForeignKey द्वारा संदर्भित वस्तुओं को प्रीफ़ेट करना केवल तभी समर्थित होता है जब क्वेरी एक ContentType तक सीमित हो।

इसके बारे में अधिक जानकारी: https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related

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