Сокращение количества запросов ORM в веб-приложении Django - PullRequest
0 голосов
/ 03 января 2019

Я пытаюсь улучшить производительность одного из моих приложений Django, чтобы они работали чуть более плавно, как часть первой итерации в улучшении того, что у меня сейчас работает. При выполнении профилирования я заметил, что у меня очень много SQL-запросов, выполняемых на нескольких страницах.

Например, на странице панели мониторинга легко выполняется более 250 запросов SQL. Дальнейшее расследование указало мне на следующий фрагмент кода в моем views.py:

for project in projects:
            for historicaldata in project.historical_data_for_n_months_ago(i):
                for key in ('hours', 'expenses'):
                    history_data[key] = history_data[key] + getattr(historicaldata, key)

Соответствующая функция в models.py файле:

def historical_data_for_n_months_ago(self, n=1):
    n_year, n_month = n_months_ago(n)

    try:
        return self.historicaldata_set.filter(year=n_year, month=n_month)
    except HistoricalData.DoesNotExist:
        return []

Как видите, это приведет к выполнению большого количества запросов для каждого проекта в списке. Первоначально это было настроено таким образом, чтобы централизованно поддерживать функциональность на уровне модели и внедрять удобные функции в приложении.

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

Заранее большое спасибо!

Редактировать По запросу, немного больше информации о связанных моделях.

Project

class Project(models.Model):
    name = models.CharField(max_length=200)
    status = models.IntegerField(choices=PROJECT_STATUS_CHOICES, default=1)
    last_updated = models.DateTimeField(default=datetime.datetime.now)

    total_hours = models.DecimalField(default=0, max_digits=10, decimal_places=2)
    total_expenses = models.DecimalField(default=0, max_digits=10, decimal_places=2)

    def __str__(self):
        return "{i.name}".format(i=self)

    def historical_data_for_n_months_ago(self, n=1):
        n_year, n_month = n_months_ago(n)

        try:
            return self.historicaldata_set.filter(year=n_year, month=n_month)
        except HistoricalData.DoesNotExist:
            return []

HistoricalData

class HistoricalData(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    year = models.IntegerField()
    month = models.IntegerField()
    hours = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    expenses = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    def __str__(self):
        return "Historical data {i.month}/{i.year} for {i.person} ({i.project})".format(i=self)

1 Ответ

0 голосов
/ 03 января 2019

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

Если вы хотите, чтобы все записи исторического_данных для проекта (обратного связаны), вам нужно использовать prefetch_related. Поскольку вам нужна определенная часть исторических данных, связанных с указанным проектом, вам необходимо использовать ее с Prefetch.

from django.db.models import Prefetch
Project.objects.prefetch_related(
    Prefetch(
        'historicaldata_set', 
        queryset=HistoricalData.objects.filter(year=n_year, month=n_month)
    )
)

После этого вы должны пройтись по этому набору данных в шаблоне django (если вы его используете). Вы также можете передать его на drf-сериализатор, и это также сделает вашу работу :)

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