Нужно ли обращаться к кэшу Django _prefetched_objects_cache, чтобы решить N+1 запрос?

У меня есть следующий код шаблона Django с N+1 запросом:

{% for theobject in objs %}
  {% for part in theobject.parts_ordered %}
      <li>{{ part }}</li>
  {% endfor %}
{% endfor %}

Вот parts_ordered на TheObject:

  class TheObject:
    # ...
    def parts_ordered(self) -> list["Part"]:
        return self.parts.all().order_by("pk")

А вот объект Part:

  class Part:
    # ...
    theobject = models.ForeignKey(
        TheObject, on_delete=models.CASCADE, related_name="parts"
    )

и вот префетч, получающий objs:

    ofs = ObjectFormSet(
        queryset=TheObject.objects
        .filter(objectset=os)
        .prefetch_related("parts")
    )

Я думаю, что order_by("pk") нарушает предварительную выборку.

Вот что рекомендует chatgpt, и это работает (больше нет N+1 запросов, результаты кажутся одинаковыми):

  class TheObject:
    # ...
    def parts_ordered(self) -> list["Part"]:
        if (
            hasattr(self, "_prefetched_objects_cache")
            and "parts" in self._prefetched_objects_cache
        ):
            # Use prefetched data and sort in Python
            return sorted(
                self._prefetched_objects_cache["parts"], key=lambda cc: cc.pk
            )

        # Fallback to querying the DB if prefetching wasn’t used
        return self.parts.all().order_by("pk")

Стоит ли мне полагаться на _prefetched_objects_cache? Есть ли лучший способ?

В вашем случае я бы не стал использовать закрытые методы или атрибуты. Как правило, авторы библиотек помечают их как закрытые по какой-либо причине. Чаще всего это детали реализации, которые могут измениться в следующей версии, что может привести к тому, что ваш код перестанет работать.

В вашем случае вы можете упростить задачу, но все равно решить проблему N + 1. Для этого вы можете использовать Prefetch object и этот запрос:

from django.db import models  
  
  
ofs = ObjectFormSet(  
    queryset=TheObject.objects  
    .filter(objectset=os)  
    .prefetch_related(  
        models.Prefetch(  
            lookup='parts',  
            queryset=Part.objects.order_by('pk'),  
            to_attr='parts_ordered',  
        ),  
    )  
)

Это даст результаты, аналогичные вашим, и должно немного повысить общую производительность, поскольку база данных выполняет сортировку, а не python.

Вернуться на верх