Как оптимизировать отложенную загрузку связанного объекта, если у нас уже есть его экземпляр? - PullRequest
1 голос
/ 12 июля 2019

Мне нравится, как Django ORM лениво загружает связанные объекты в наборе запросов, но я думаю, что это довольно непредсказуемо. API набора запросов не сохраняет связанные объекты, когда они используются для создания набора запросов, тем самым извлекая их снова при последующем доступе.

Предположим, у меня есть экземпляр ModelA (скажем, instance_a ), который является внешним ключом (скажем, for_a ) из нескольких N экземпляров ModelB . Теперь я хочу выполнить запрос к ModelB , у которого в качестве внешнего ключа задан экземпляр ModelA .

Django ORM предоставляет два способа:

  • Использование .filter() на ModelB :
b_qs = ModelB.objects.filter(for_a=instance_a)
for instance_b in b_qs:
    instance_b.for_a # <-- fetches the same row for ModelA again

Результатов здесь 1 + N запросов.

  • Использование обратных отношений на ModelA экземпляр:
b_qs = instance_a.for_a_set.all()
for instance_b in b_qs:
    instance_b.for_a # <-- this uses the instance_a from memory

Результаты только для 1 запроса здесь.

Хотя для достижения результата можно использовать второй способ, он не является частью стандартного API и не применим для каждого сценария. Например, если у меня есть экземпляры 2 внешних ключей ModelB (скажем, ModelA и ModelC ), и я хочу получить связанные объекты для них обоих. Что-то вроде следующих работ:

ModelB.objects.filter(for_a=instance_a, for_c=instance_c)

Полагаю, для этого сценария можно использовать .intersection(), но я бы хотел добиться этого через стандартный API. В конце концов, для покрытия таких случаев потребуется больше кода с нестандартными функциями набора запросов, что может не иметь смысла для следующего разработчика.

Итак, первый вопрос, возможно ли оптимизировать такие сценарии с помощью самого стандартного API? Второй вопрос, если сейчас это невозможно, можно ли его добавить с помощью QuerySet с помощью некоторых настроек?

PS: Я впервые задаю вопрос здесь, так что извините, если я допустил ошибку.

Ответы [ 2 ]

1 голос
/ 12 июля 2019

Вы используете .select_related(..) [Джанго-док] для ForeignKey с или .prefetch_related(..) [Джанго-док] для отношений «что-то-ко-многим».

С помощью .select_related(..) вы создадите LEFT OUTER JOIN на стороне базы данных и получите записи для двух объектов и, таким образом, выполните десериализацию для соответствующих объектов.

ModelB.objects.<b>select_related('for_a')</b>.filter(for_a=instance_a)

Для отношений «один ко многим» (т.е. наоборот ForeignKey) или ManyToManyField с это не очень хорошая идея, поскольку это может привести к большому количеству дубликатов.объекты, которые извлекаются.Это привело бы к большому ответу из базы данных и большой работе на стороне Python по десериализации этих объектов..prefetch_related сделает отдельные запросы, а затем выполнит связывание.

1 голос
/ 12 июля 2019

Вы можете улучшить запрос, используя select_related():

b_qs = ModelB.objects.select_related('for_a').filter(for_a=instance_a)

или

b_qs = instance_a.for_a_set.select_related('for_a')

Это помогает?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...