Spec-Zone.ru › Django 1.10

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

Слой базы данных 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

Например:

  • На самом базовом уровне используйте filter и exclude для фильтрации в базе данных.
  • Используйте 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() и используйте их:

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

Не получайте то, что вам не нужно

Используйте 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. Поскольку QuerySets ленивые, это не выполняет запросы к базе данных, если «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.10/topics/db/optimization/

Spec-Zone.ru

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