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

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

Сначала профиль

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

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

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

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

…включая:

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

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

Поймите QuerySet s

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

Понять QuerySet оценку

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

Что такое кэшированные атрибуты

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

>>> 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() может помочь.

Используйте explain()

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

Работа с базой данных в базе данных, а не в Python

Например:

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

Используйте RawSQL

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

Используйте необработанный SQL

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

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

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

Итак, используя примеры моделей блогов :

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

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

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

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

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

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

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

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

Получите все сразу, если знаете, что оно вам понадобится

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

Не забирайте вещи, которые вам не нужны

Используйте QuerySet.values() и values_list()

Если вам нужно только dict или list значений и не нужны объекты модели ORM, используйте подходящее использование values() . Они могут быть полезны для замены объектов модели в коде шаблона - до тех пор, пока предоставляемые вами dicts имеют те же атрибуты, что и используемые в шаблоне, все в порядке.

Используйте QuerySet.defer() и only()

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

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

Используйте QuerySet.count()

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

Используйте QuerySet.exists()

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

Но:

Не злоупотребляйте count() и exists()

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

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

{% 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 имеет значение False.
  2. Использование with означает, что мы сохраняем user.emails.all переменную для последующего использования, что позволяет повторно использовать ее кэш.
  3. Строка вызывает вызов, который вызывает выполнение запроса в базе данных и, по крайней мере, первую строку, которая будет преобразована в объект ORM. Если результатов нет, он вернет False, в противном случае - True.{% if emails %} QuerySet.__bool__() user.emails.all()
  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() без параметров.

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

Используйте массовые методы

Используйте массовые методы, чтобы уменьшить количество операторов SQL.

Создать оптом

При создании объектов по возможности используйте этот 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

Массовое обновление

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

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

Следующий пример:

entries[0].headline = 'This is not a test'
entries[1].headline = 'This is no longer a test'
Entry.objects.bulk_update(entries, ['headline'])

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

entries[0].headline = 'This is not a test'
entries[0].save()
entries[1].headline = 'This is no longer a test'
entries[1].save()

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

Вставить оптом

При вставке объектов в ManyToManyFields используйте add() с несколькими объектами, чтобы уменьшить количество SQL-запросов. Например:

my_band.members.add(me, my_friend)

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

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

… Где Bands и Artists иметь отношения «многие ко многим».

При вставке разных пар объектов в ManyToManyField или при определении настраиваемой through таблицы используйте bulk_create() метод для уменьшения количества запросов SQL. Например:

PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.bulk_create([
    PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
    PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
    PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
], ignore_conflicts=True)

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

my_pizza.toppings.add(pepperoni)
your_pizza.toppings.add(pepperoni, mushroom)

… Где Pizza и Topping иметь отношения «многие ко многим». Обратите внимание, что их несколько , поэтому убедитесь, что они подходят для вашего варианта использования.caveats to this method

Удалить оптом

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

my_band.members.remove(me, my_friend)

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

my_band.members.remove(me)
my_band.members.remove(my_friend)

… Где Bands и Artists иметь отношения «многие ко многим».

При удалении различных пар объектов из ManyToManyFields , использование delete() на Q выражение с несколькими through экземплярами модели , чтобы уменьшить количество запросов SQL. Например:

from django.db.models import Q
PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.filter(
    Q(pizza=my_pizza, topping=pepperoni) |
    Q(pizza=your_pizza, topping=pepperoni) |
    Q(pizza=your_pizza, topping=mushroom)
).delete()

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

my_pizza.toppings.remove(pepperoni)
your_pizza.toppings.remove(pepperoni, mushroom)

… Где Pizza и Topping иметь отношения «многие ко многим».

Copyright ©2021 All rights reserved