Spec-Zone.ru › Django 1.11

Оптимизация доступа к базе данных

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

Профилирование в первую очередь

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

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

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

Использование стандартных методов оптимизации БД

…включая:

  • Индексы. Это приоритетный пункт, после того как вы определили из профилирования, какие индексы необходимо добавить. Используйте Field.db_index или Meta.index_together для добавления их из Django. Подумайте о добавлении индексов к полям, которые вы часто используете в запросах с помощью filter(), exclude(), order_by() и т. д., так как индексы могут помочь ускорить поиск. Обратите внимание, что определение лучших индексов — это сложная тема, зависящая от базы данных, которая будет зависеть от вашего конкретного приложения. Накладные расходы на поддержание индекса могут перевесить любые преимущества в скорости запроса.
  • Правильное использование типов полей.

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

Понимание QuerySet

Понимание QuerySets имеет решающее значение для обеспечения хорошей производительности с помощью простого кода. В частности:

Понимание QuerySet оценки

Чтобы избежать проблем с производительностью, важно понять:

  • что QuerySets ленивы.
  • когда они оцениваются.
  • как данные хранятся в памяти.

Понимание кэшированных атрибутов

Помимо кэширования всего QuerySet, существует кэширование результата атрибутов объектов ORM. В общем случае атрибуты, которые не являются вызываемыми, будут кэшироваться. Например, предполагая пример модели Weblog:

>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # Blog object is retrieved at this point
>>> entry.blog   # cached version, no DB access

Но в общем случае вызываемые атрибуты вызывают поиск в базе данных каждый раз:

>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # query performed
>>> entry.authors.all()   # query performed again

Будьте внимательны при чтении кода шаблона — система шаблонов не позволяет использовать скобки, но автоматически вызовет вызываемые элементы, скрывая приведенное выше различие.

Будьте осторожны с собственными настраиваемыми свойствами — вам необходимо реализовать кэширование по мере необходимости, например, с помощью декоратора cached_property.

Используйте with тег шаблона

Чтобы использовать поведение кэширования QuerySet, вам может потребоваться использовать тег шаблона with.

Использование iterator()

При большом количестве объектов поведение кэширования QuerySet может привести к большому использованию памяти. В этом случае iterator() может помочь.

Выполняйте работу с базой данных в базе данных, а не в Python

Например:

  • На самом базовом уровне используйте фильтры и исключения для фильтрации в базе данных.
  • Используйте F expressions для фильтрации на основе других полей в той же модели.
  • Используйте annotate для агрегации в базе данных.

Если этого недостаточно для генерации необходимого SQL:

Использование RawSQL

Менее переносимый, но более мощный метод — это выражение RawSQL, которое позволяет явно добавить некоторый SQL в запрос. Если этого все еще недостаточно:

Использование необработанного SQL

Напишите свой собственный собственный SQL для извлечения данных или заполнения моделей. Используйте django.db.connection.queries для того, чтобы узнать, что Django записывает для вас, и начните с этого.

Получение отдельных объектов с помощью уникального индексированного столбца

Есть две причины для использования столбца с unique или db_index при использовании get() для получения отдельных объектов. Во-первых, запрос будет быстрее из-за базового индекса базы данных. Во-вторых, запрос может выполняться значительно медленнее, если несколько объектов соответствуют поиску; наличие уникального ограничения в столбце гарантирует, что этого никогда не произойдет.

Таким образом, используя пример модели Weblog:

>>> entry = Entry.objects.get(id=10)

будет быстрее, чем:

>>> entry = Entry.objects.get(headline="News Item Title")

потому что id индексирован базой данных и гарантированно уникален.

Выполнение следующего потенциально довольно медленное:

>>> entry = Entry.objects.get(headline__startswith="News")

Во-первых, headline не индексируется, что замедлит базовый запрос базы данных.

Во-вторых, запрос не гарантирует, что будет возвращен только один объект. Если запрос соответствует более чем одному объекту, он извлечет и передаст все из них из базы данных. Эта цена может быть существенной, если возвращается сотни или тысячи записей. Эта цена будет усугубляться, если база данных находится на отдельном сервере, где также играют роль сетевые накладные расходы и задержка.

Извлекайте все сразу, если вы знаете, что вам это потребуется

Обращение к базе данных несколько раз для разных частей одного «набора» данных, которые вам нужны все части, как правило, менее эффективно, чем извлечение всего за один запрос. Это особенно важно, если у вас есть запрос, который выполняется в цикле, и, следовательно, может выполняться много запросов к базе данных, когда нужен только один. Поэтому:

Использование QuerySet.select_related() и prefetch_related()

Полноценно изучите select_related() и prefetch_related() и используйте их:

  • в менеджерах и менеджерах по умолчанию по мере необходимости. Понимайте, когда ваш менеджер используется, а когда нет; иногда это сложно, поэтому не делайте предположений.
  • в коде представления или других слоях, возможно, используя prefetch_related_objects(), где это необходимо.

Не извлекайте то, что вам не нужно

Использование QuerySet.values() и values_list()

Когда вам нужна только dict или list значений, и вам не нужны объекты модели ORM, используйте метод values(). Это может быть полезно для замены объектов модели в коде шаблонов — достаточно, чтобы словари, которые вы передаёте, имели те же атрибуты, что и те, которые используются в шаблоне.

Использование QuerySet.defer() и only()

Используйте defer() и only(), если вам известны столбцы базы данных, которые вам не нужны (или, как правило, не нужны), чтобы избежать их загрузки. Обратите внимание, что если вы используете эти методы, ORM придётся получить их в отдельном запросе, что может ухудшить производительность при неправильном использовании.

Также имейте в виду, что при создании модели с отложенными полями в Django есть некоторые (небольшие дополнительные) накладные расходы. Не стоит чрезмерно активно откладывать поля без предварительного профилирования, так как базе данных придётся прочитать большую часть данных, отличных от текстовых и VARCHAR, с диска для каждой строки результатов, даже если в итоге будут использованы только несколько столбцов. Методы defer() и only() наиболее полезны, когда вы можете избежать загрузки большого объёма текстовых данных или для полей, преобразование которых в Python может потребовать значительных ресурсов. Как всегда, сначала сделайте профилирование, затем оптимизируйте.

Использование QuerySet.count()

…если вам нужно только количество, а не len(queryset).

Использование QuerySet.exists()

…если вам нужно только узнать, существует ли хотя бы один результат, а не if queryset.

Но:

Избегайте чрезмерного использования count() и exists()

Если вам понадобятся другие данные из QuerySet, просто вычислите их.

Например, предполагая модель Email, у которой есть атрибут body и связь многие ко многим с User, следующий код шаблона оптимален:

{% if display_inbox %}
  {% with emails=user.emails.all %}
    {% if emails %}
      <p>You have {{ emails|length }} email(s)</p>
      {% for email in emails %}
        <p>{{ email.body }}</p>
      {% endfor %}
    {% else %}
      <p>No messages today.</p>
    {% endif %}
  {% endwith %}
{% endif %}

Он оптимален, потому что:

  1. Поскольку QuerySet ленивые, при False значении ‘display_inbox’ запросов к базе данных не выполняется.
  2. Использование with означает, что мы сохраняем user.emails.all в переменную для последующего использования, что позволяет повторно использовать кэш.
  3. Строка {% if emails %} вызывает QuerySet.__bool__(), что запускает запрос user.emails.all() к базе данных, и по крайней мере первая строка преобразуется в объект ORM. Если результатов нет, возвращается False, в противном случае True.
  4. Использование {{ emails|length }} вызывает QuerySet.__len__(), заполняя остальную часть кэша без выполнения дополнительного запроса.
  5. Цикл for итерируется по уже заполненному кэшу.

В целом, этот код выполняет один или ноль запросов к базе данных. Единственной преднамеренной оптимизацией является использование тега with. Использование QuerySet.exists() или QuerySet.count() в любой точке приведёт к дополнительным запросам.

Использование QuerySet.update() и delete()

Вместо извлечения большого количества объектов, изменения значений и сохранения их по отдельности, используйте оператор SQL UPDATE в масштабе, с помощью QuerySet.update(). Аналогично, делайте удаление в объёме где это возможно.

Однако обратите внимание, что эти методы массового обновления не могут вызывать методы save() или delete() отдельных экземпляров, что означает, что любая добавленная вами пользовательская логика для этих методов не будет выполнена, включая всё, что зависит от обычных сигналов базы данных сигналов.

Использование значений внешних ключей напрямую

Если вам нужен только значение внешнего ключа, используйте значение внешнего ключа, которое уже есть в объекте, а не получайте весь связанный объект и его первичный ключ. То есть, делайте:

entry.blog_id

вместо:

entry.blog.id

Не сортируйте результаты, если вам это не нужно

Сортировка не бесплатна; каждое поле для сортировки — это операция, которую база данных должна выполнить. Если у модели есть стандартная сортировка (Meta.ordering), а вам она не нужна, удалите её в QuerySet, вызвав order_by() без параметров.

Добавление индексов в базу данных может помочь в улучшении производительности сортировки.

Массовое создание

При создании объектов используйте метод bulk_create(), где это возможно, чтобы уменьшить количество запросов SQL. Например:

Entry.objects.bulk_create([
    Entry(headline='This is a test'),
    Entry(headline='This is only a test'),
])

…предпочтительнее, чем:

Entry.objects.create(headline='This is a test')
Entry.objects.create(headline='This is only a test')

Обратите внимание, что существует ряд caveats to this method, поэтому убедитесь, что это подходит для вашего случая.

Это также относится к ManyToManyFields, поэтому:

my_band.members.add(me, my_friend)

…предпочтительнее, чем:

my_band.members.add(me)
my_band.members.add(my_friend)

…где Bands и Artists имеют отношение многие ко многим.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/topics/db/optimization/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API