Django ORM: оптимизация запросов, включающих отношения «многие ко многим»

StackOverflow https://stackoverflow.com/questions/1642092

Вопрос

У меня есть следующая структура модели:

class Container(models.Model):
    pass

class Generic(models.Model):
    name = models.CharacterField(unique=True)
    cont = models.ManyToManyField(Container, null=True)
    # It is possible to have a Generic object not associated with any container, 
    # thats why null=True

class Specific1(Generic):
    ...

class Specific2(Generic):
    ...

...

class SpecificN(Generic):
    ...

Скажем, мне нужно получить все модели типа Specific , которые связаны с конкретным контейнером.

SQL для этого более или менее тривиален, но это не вопрос. К сожалению, я не очень опытен в работе с ORM (в частности, с ORM в Django), поэтому я могу пропустить шаблон здесь.

Когда все сделано грубо, -

c = Container.objects.get(name='somename') # this gets me the container
items = c.generic_set.all() 
# this gets me all Generic objects, that are related to the container
# Now what? I need to get to the actual Specific objects, so I need to somehow
# get the type of the underlying Specific object and get it
for item in items:
    spec = getattr(item, item.get_my_specific_type())

это приводит к множеству попаданий в дБ (по одному для каждой общей записи, относящейся к контейнеру), так что это явно не тот способ, которым это можно сделать. Теперь, возможно, это можно сделать путем непосредственного получения объектов SpecificX:

s = Specific1.objects.filter(cont__name='somename')
# This gets me all Specific1 objects for the specified container
...
# do it for every Specific type

таким образом, БД будет поражен один раз для каждого конкретного типа (я думаю, что это приемлемо).

Я знаю, что .select_related () не работает с отношениями m2m, поэтому здесь это не сильно поможет.

Чтобы повторить, конечный результат должен быть коллекцией объектов SpecificX (не Generic).

Это было полезно?

Решение

Я думаю, что вы уже обрисовали две простые возможности. Либо вы делаете один запрос фильтра к универсальному, а затем приводите каждый элемент к его определенному подтипу (в результате получается n + 1 запросов, где n - количество возвращаемых элементов), либо вы делаете отдельный запрос для каждой конкретной таблицы (приводит к k запросов, где k - это число определенных типов).

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

Если второе на самом деле значительно быстрее для вашего варианта использования, и вы готовы проделать некоторую дополнительную работу по очистке кода, должна быть возможность написать собственный метод менеджера для общей модели, который "предварительно" извлекает & Quot; все данные подтипа из соответствующих специфических таблиц для данного набора запросов, используя только один запрос на таблицу подтипа; подобно тому, как этот фрагмент оптимизирует общие внешние ключи с помощью массовой предварительной выборки. Это даст вам те же запросы, что и во втором варианте, с синтаксисом DRYer для первого варианта.

Другие советы

Не полный ответ, но вы можете избежать большого количества хитов, делая это

items= list(items)
for item in items:
    spec = getattr(item, item.get_my_specific_type())

вместо этого:

for item in items:
    spec = getattr(item, item.get_my_specific_type())

Действительно, принудительно приводя к списку Python, вы заставляете django orm загружать все элементы в вашем наборе запросов. Затем он делает это в одном запросе.

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

http://lazypython.blogspot.com/2008 /11/timeline-view-in-django.html

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top