Производительность django при запросе моделей со многими внешними ключами?

Создаем службу, которая должна поддерживать что-то в соответствии с системой отслеживания случаев. Здесь наша модель:

class Incident(models.Model):    
    title = models.CharField(max_length=128)
    category = models.ForeignKey(Category)
    status = models.ForeignKey(Status)    
    severity = models.ForeignKey(Severity)
    owned_by = models.ForeignKey(User, related_name="owned_by", null=True, blank=True)   
    next_action = models.ForeignKey(IncidentAction)    
    created_date  = models.DateTimeField()
    created_by = models.ForeignKey(User, related_name="opened_by")    
    last_edit_date = models.DateTimeField(null=True, blank=True)
    last_edit_by = models.ForeignKey(User, related_name="last_edit_by", null=True, blank=True)        
    closed_date  = models.DateTimeField(null=True, blank=True)
    closed_by = models.ForeignKey(User, related_name="Closed by", null=True, blank=True)

Поскольку в эту модель втягивается много внешних ключей, это делает интересными sql-запросы. Мы использовали в качестве пробной версии сетку данных djblets и панель инструментов отладки django и были встревожены множеством запросов, поражающих каждый мы добавляем новый столбец для представления, который использует внешний ключ, в основном этот тип рабочего процесса запроса:

#prepare the grid
select * from incident_table;
#render each row
for each row in incident table
    for each column that is a foreign key select row from foreign table with id

Он выполняет дополнительный запрос выбора для каждой строки для каждого столбца, который пытается вытащить свойство для внешнего ключа.

Я думаю, что это универсальная проблема с django и его ORM в отношении вытягивания свойств из моделей внешнего ключа для отображения. В качестве теста я сбросил сетку данных и просто выполнил простой список свойств для набора запросов, и увидел, что запрос balloon аналогичным образом.

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

Некоторые оптимизации, которые мы пробовали, были:

  • django-orm-cache: Кажется, что не работает с django 1.0.4
  • django-caching: Это хорошо работает для кэширования часто задаваемых моделей
  • просмотр кэширования уровня с помощью memcached
  • Изменить: с помощью select_related() может ускорить создание шаблона, не возвращаясь обратно в базу данных обратно, но похоже, что он просто задерживает внешние ключи в исходном запросе, используя единственный запрос для внешнего ключа. Просто кажется, что переместил запрос на несколько баз данных раньше времени.

Но есть несколько более глубоких вопросов, на которые мы призываем мудрость толпы:

  • Для моделей с тоннами внешних ключей, каков наилучший способ заставить его эффективно запрашивать свойства у внешних ключей?
    • Является ли кэширование зависимых моделей единственным способом перехода с использованием вышеперечисленных систем кэширования ORM?
    • Или это стандартный случай перерастания ORM и необходимость свертывания собственного пользовательского SQL-запроса с объединениями, чтобы как можно более эффективно получить желаемый вывод данных?

Вопросы, связанные с кешированием и внешними ключами:

DB/performance: макет модели django, которая редко ссылается на своего родителя более одного раза, Django ORM: кэширование и управление объектами ForeignKey:

Ответ 1

select_related() - правильное решение; вы ошибаетесь в том, как он должен работать. Я думаю, что вы не используете select_related правильно, если вы все еще получаете несколько запросов по указанному FK. Быстрый журнал сеанса Python (у Studio есть FK для django.auth.user здесь):

>>> from django.db import connection
>>> studios = Studio.objects.all().select_related('user')
>>> for studio in studios:
>>>     print studio.user.email
>>>        
[email protected]
[email protected]
>>> len(connection.queries) 
1

Итак, я получил список объектов Studio (2 в моей тестовой базе данных) и получил пользователя для каждого из них в одном запросе SQL. Без вызова select_related() требуется три запроса.

Обратите внимание, что select_related не обрабатывает отношения "многие ко многим" - хотя я думаю, что вы можете вручную запросить промежуточную таблицу m2m следовать этим FKs в любом направлении, не требуя дополнительного запроса, если вы в порядке, начиная свой запрос от промежуточного объекта. Может быть, что тебя ловят? Вы указали только отношения FK, а не m2ms, поэтому я дал простую иллюстрацию этого.