Django повторяющиеся запросы с менеджером, связанным - PullRequest
0 голосов
/ 04 мая 2018

Я работаю над оптимизацией моих запросов ORM. У меня есть два приложения, «app1» и «app2». Один класс 'app2' имеет внешний ключ к классу app1 следующим образом:

#app1/models.py
class C1App1(WithDateAndOwner):
  def get_c2_app2(self):
    res = self.c2app2_set.all()
    if res.count() > 0:
      return res[0]
    else:
      return None

#app2/models.py
class C2App2(WithDateAndOwner):
  c1app1 = models.ForeignKey("app1.C1App1")
  is_ok = models.BooleanField(default=False)

Теперь я отображаю C2App2 для всех экземпляров C1App1 на странице администратора:

#app1/admin.py
@admin.register(C1App1)
class C1App1Admin(admin.MyAdmin):
  list_display = ("get_c2_app2")
  list_select_related = ()
  list_prefetch_related = ("c2app2_set",)
  list_per_page = 10

prefetch_related уменьшает этот запрос:

SELECT ••• FROM `app2_c2app2` WHERE `app2_c2app2`.`c1app1_id` = 711
  Duplicated 19 times.

до:

SELECT ••• FROM `app2_c2app2` WHERE `app2_c2app2`.`c1app1_id` IN (704, 705, 706, 707, 708, 709, 710, 711, 702, 703) ORDER BY `app2_c2app2`.`id` DESC

И это нормально. Теперь, если я хочу отфильтровать запрос по атрибуту C2App2 'is_ok':

#app1/models.py
class C1App1(WithDateAndOwner):
  def get_c2_app2(self):
    res = self.c2app2_set.filter(is_ok=False)
    if res.count() > 0:
      return res[0]
    else:
      return None

У меня все еще есть этот предварительно выбранный запрос:

SELECT ••• FROM `c2app2_set` WHERE `app2_c2app2`.`c1app1_id` IN (704, 705, 706, 707, 708, 709, 710, 711, 702, 703) ORDER BY `app2_c2app2`.`id` DESC

но с этим дублируется для каждого отображаемого экземпляра C1App1 (10). :

SELECT ••• FROM `app2_c2app2` WHERE (`app2_c2app2`.`c1app1_id` = 711 AND `app2_c2app2`.`is_ok` = 1)
  Duplicated 13 times.

На самом деле запрос также дублируется снова для 3 идентификаторов из 10 отображаемых, что приводит к этим 13 дублированным запросам. Что я могу сделать, чтобы не дублировать эти запросы? Похоже, prefetch_related здесь больше не помогает.

1 Ответ

0 голосов
/ 04 мая 2018

prefetch_related работает только при использовании .all(). Если вы примените любые другие преобразования, такие как .filter(), будет выполнен новый запрос к БД. Это потому, что prefetch_related просто кэширует ВСЕ связанные экземпляры в списке, поэтому Django не может выполнить filter() в списке. Для решения вашей проблемы вы должны использовать объект Prefetch . Вы можете передать ему queryset параметр. Поэтому вместо использования list_prefetch_related переопределите метод get_queryset в своем классе администратора.

def get_queryset(*args, **kwargs):
     qs = super().get_queryset(*args, **kwargs)
     qs = qs.prefetch_related(Prefetch('c2app2_set', queryset=C2App2.objects.filter(is_ok=False)))
     return qs

И

class C1App1(WithDateAndOwner):
   def get_c2_app2(self):
      res = self.c2app2_set.all()
      if res.count() > 0:
         return res[0]
      else:
         return None
...