Django ORM'de select_related ve prefetch_related arasındaki fark nedir?


292

Django doktorunda,

select_related() yabancı anahtar ilişkilerini "izler" ve sorguyu yürütürken ilgili ek nesne verilerini seçer.

prefetch_related() her ilişki için ayrı bir arama yapar ve Python'da "birleştirme" yapar.

"Python'a katılmak" ne demek? Birisi bir örnekle açıklayabilir mi?

Anladığım kadarıyla yabancı anahtar ilişkisi için kullanım select_related; ve M2M ilişkisi için kullanın prefetch_related. Bu doğru mu?


2
Birleştirmenin python içinde gerçekleştirilmesi, birleştirmenin veritabanında gerçekleşmeyeceği anlamına gelir. Select_related ile birleştirmeniz veritabanında gerçekleşir ve yalnızca bir veritabanı sorgusu yaşarsınız. Prefetch_related ile, iki sorgu yürüteceksiniz ve daha sonra sonuçlar ORM tarafından 'birleştirilecek', böylece yine de object.related_set
Mark Galloway

3
Bir dipnot olarak Timmy O'Mahony, veritabanı hitlerini kullanarak da farklılıklarını açıklayabilir: link
Mærcos

Yanıtlar:


424

Anlayışınız çoğunlukla doğrudur. Sen kullanmak select_relatedsize seçme olacağız nesne tek bir nesne, yani ne zaman OneToOneFieldya da ForeignKey. Bir prefetch_relatedşeylerin "setini" alacağınız zaman, ManyToManyFieldbelirttiğiniz veya tersini yaptığınız gibi kullanırsınız ForeignKey. Sadece "reverse ForeignKeys" ile ne demek istediğimi açıklığa kavuşturmak için bir örnek:

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

Aradaki fark, select_relatedSQL'in birleşmesi ve bu nedenle sonuçları SQL sunucusundan tablonun bir parçası olarak geri almasıdır. prefetch_relatedÖte yandan başka bir sorgu yürütür ve bu nedenle orijinal nesnede ( ModelAyukarıdaki örnekte) gereksiz sütunları azaltır . Kullanabileceğiniz prefetch_relatedherhangi bir şey select_relatediçin kullanabilirsiniz .

Ödünç alma işlemleri prefetch_related, sunucuya geri dönmek için bir kimlik listesi oluşturmak ve göndermek zorundadır, bu biraz zaman alabilir. Bir işlemde bunu yapmanın güzel bir yolu olup olmadığından emin değilim, ancak anlayışım Django'nun her zaman sadece bir liste gönderdiğini ve SEÇ olduğunu söyledi ... NEREDE pk IN (..., ..., ...) temelde. Bu durumda, önceden getirilmiş veriler seyrekse (diyelim ki insanların adreslerine bağlı ABD Devlet nesneleri) bu çok iyi olabilir, ancak bire bire yakınsa, bu çok fazla iletişim gerektirebilir. Şüpheniz varsa, ikisini de deneyin ve hangisinin daha iyi performans gösterdiğine bakın.

Yukarıda tartışılan her şey temel olarak veritabanı ile iletişim ile ilgilidir. Ancak Python tarafında prefetch_related, veritabanındaki her bir nesneyi temsil etmek için tek bir nesnenin kullanıldığı ek bir yararı vardır. İle select_relatedyinelenen nesneleri her bir "üst" nesne için Python oluşturulacaktır. Python'daki nesnelerin bellek yükü iyi olduğundan, bu da dikkate alınabilir.


3
daha hızlı ne var?
elad gümüş

24
select_relatedbir sorgu prefetch_relatediki iken , birincisi daha hızlıdır. Ama 'sselect_relatedManyToManyField
bhinesley

31
@eladsilver Yavaş cevap için özür dilerim. Aslında bağlıdır. select_relatedSQL'de bir JOIN kullanır, ancak prefetch_relatedsorguyu ilk modelde çalıştırır, önceden getirmesi gereken tüm kimlikleri toplar ve ardından WHERE içindeki bir IN yan tümcesi ile bir sorgu çalıştırır. Aynı yabancı anahtarı kullanan 3-5 model söylediyseniz select_related, neredeyse kesinlikle daha iyi olacaktır. Aynı yabancı anahtarı kullanan 100'lü veya 1000'li modelleriniz varsa, prefetch_relatedaslında daha iyi olabilir. Arasında ne olduğunu test etmek ve görmek zorunda kalacaksınız.
CrazyCasta

1
Önceden getirme ile ilgili "genellikle pek mantıklı değil" hakkındaki yorumunuza itiraz ediyorum. Bu, benzersiz olarak işaretlenmiş FK alanları için geçerlidir, ancak birden fazla satırın aynı FK değerine (yazar, kullanıcı, kategori, şehir vb.) Sahip olduğu her yerde, önceden getirme, Django ve DB arasındaki bant genişliğini azaltır, ancak yinelenen satırları azaltır. Ayrıca genellikle DB'de daha az bellek kullanır. Bunlardan her ikisi de tek bir ekstra sorgunun yükünden daha önemlidir. Bu oldukça popüler bir sorunun üst cevabı göz önüne alındığında, bu cevabın not edilmesi gerektiğini düşünüyorum.
Gordon Wrigley

1
@GordonWrigley Evet, yazdığımdan bu yana bir süre geçti, bu yüzden geri döndüm ve biraz açıklığa kavuştum. "DB daha az bellek kullanır" bit, ama her şeye evet katılıyorum emin değilim. Ve Python tarafında daha az bellek kullanacağından emin olabilirsiniz.
CrazyCasta

26

Her iki yöntem de, gereksiz db sorgularından vazgeçmek için aynı amaca ulaşır. Ancak verimlilik için farklı yaklaşımlar kullanırlar.

Bu yöntemlerden herhangi birini kullanmanın tek nedeni, tek bir büyük sorgunun birçok küçük soruna tercih edilebilir olmasıdır. Django büyük sorguyu veritabanında talep sorguları yerine önbellekte modeller oluşturmak için kullanır.

select_relatedher aramayla birleştirme gerçekleştirir, ancak seçimi birleştirilen tüm tabloların sütunlarını içerecek şekilde genişletir. Ancak bu yaklaşımın bir uyarısı vardır.

Birleşimler, bir sorgudaki satır sayısını çarpma potansiyeline sahiptir. Yabancı anahtar veya bire bir alanda birleştirme gerçekleştirdiğinizde, satır sayısı artmaz. Ancak, çoktan çoğa birleşme bu garantiye sahip değildir. Bu nedenle, Django select_relatedbeklenmedik bir şekilde büyük bir birleşmeye yol açmayacak ilişkilerle kısıtlanıyor .

"Piton katılmak" için prefetch_relateddaha sonra olması gereken alarm biraz. Birleştirilecek her tablo için ayrı bir sorgu oluşturur. Bu tabloların her birini bir WHERE IN yan tümcesi ile filtreler, örneğin:

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

Potansiyel olarak çok fazla satır içeren tek bir birleştirme gerçekleştirmek yerine, her tablo ayrı bir sorguya bölünür.


1

Zaten gönderilen cevapları geçti. Sadece gerçek örnekle bir cevap eklersem daha iyi olacağını düşündüm.

Diyelim ki ilgili 3 Django modeliniz var.

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)

Burada sorgulayabilir M2modeli ve onun göreceli M1kullanarak nesneleri select_relationalanını ve M3kullanan nesnelerin prefetch_relationalanını.

Daha önce de belirttiğimiz Ancak olarak M1gelen 'ın ilişkisini M2bir olduğunu ForeignKey, sadece sadece döner 1 herhangi bir kayıt M2nesnesi. Aynı şey için OneToOneFieldde geçerlidir .

Fakat M3'dan ilişkisi herhangi bir sayıdaki nesneyi döndüren M2bir ilişkidir .ManyToManyFieldM1

Eğer 2 var bir durumu düşünün M2nesneleri m21, m22aynısından 5 ilişkili M3kimlikleri ile nesneleri 1,2,3,4,5. Bu M3nesnelerin her biri için ilişkili nesneleri getirdiğinizde M2, related ile ilgili seçeneğini kullanırsanız, bu nasıl çalışır.

Adımlar:

  1. m21Nesneyi bul .
  2. Kimlikleri olan M3nesnelerle ilgili tüm nesneleri sorgulayın .m211,2,3,4,5
  3. Aynı şeyi m22nesne ve diğer tüm M2nesneler için tekrarlayın .

Biz aynı olması gibi 1,2,3,4,5her ikisi için kimlikleri m21, m22biz seçeneği select_related kullanmak eğer, zaten getirilen vardı aynı kimlikler için iki kez DB sorgulamak için gidiyor, itiraz ediyor.

Bunun yerine prefetch_related kullanırsanız, M2nesneleri almaya çalıştığınızda , M2tablo sorgulanırken ve son adım olarak Django M3tabloya bir sorgu yapacak. M2nesnelerinizin döndürdüğü tüm kimlikler kümesiyle . ve bunları M2veritabanı yerine Python kullanarak nesnelere birleştirin .

Bu şekilde, tüm M3nesneleri yalnızca bir kez sorgulayarak performansı artırırsınız.


0

Django belgelerinin dediği gibi:

prefetch_related ()

Belirtilen aramaların her biri için ilgili nesneleri otomatik olarak tek bir toplu işte alacak bir Sorgu Kümesi döndürür.

Her ikisi de ilgili nesnelere erişmenin neden olduğu veritabanı sorgularının huzursuzluğunu durdurmak için tasarlandığından, select_related'e benzer bir amacı vardır, ancak strateji oldukça farklıdır.

select_related, bir SQL birleşimi oluşturarak ve SELECT deyiminde ilgili nesnenin alanlarını ekleyerek çalışır. Bu nedenle, select_related, ilgili nesneleri aynı veritabanı sorgusuna alır. Bununla birlikte, bir 'çok' ilişkide birleşmekten kaynaklanacak çok daha büyük sonuç kümesinden kaçınmak için, select_related, tek değerli ilişkilerle (yabancı anahtar ve bire bir) sınırlıdır.

Öte yandan prefetch_related, her ilişki için ayrı bir arama yapar ve Python'da 'birleştirme' yapar. Bu, yabancı anahtar ve select_related tarafından desteklenen bire bir ilişkilere ek olarak, select_related kullanılarak gerçekleştirilemeyen çoktan çoğa ve çoktan bire nesnelerin önceden getirilmesini sağlar. Ayrıca GenericRelation ve GenericForeignKey öğelerinin önceden getirilmesini destekler, ancak homojen bir sonuç kümesiyle sınırlandırılmalıdır. Örneğin, bir GenericForeignKey tarafından başvurulan önceden getirilmiş nesneler yalnızca sorgu bir ContentType ile sınırlıysa desteklenir.

Bununla ilgili daha fazla bilgi: https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.