производительность 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

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

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

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

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

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

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

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

Связанные вопросы, которые поставили вопросы при кэшировании и внешних ключах:

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

11
задан Community 23 May 2017 в 12:16
поделиться

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@notadomain.com
anotheremail@notadomain.com
>>> len(connection.queries) 
1

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

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

12
ответ дан 3 December 2019 в 09:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: