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

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

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

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

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

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

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

…в том числе:

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

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

Поймите QuerySets

Понимание 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, немедленно оцените их.

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

if display_emails:
    emails = user.emails.all()
    if emails:
        print('You have', len(emails), 'emails:')
        for email in emails:
            print(email.subject)
    else:
        print('You do not have any emails.')

Это оптимально, потому что:

  1. Поскольку QuerySets ленивы, он не выполняет запросов к базе данных, если display_emailsесть False.
  2. Сохранение user.emails.all()в emailsпеременной позволяет повторно использовать ее кэш результатов.
  3. Строка вызывает вызов, который вызывает выполнение запроса в базе данных. Если результатов нет, он вернется , в противном случае .if emailsQuerySet.__bool__()user.emails.all()FalseTrue
  4. Использование len(emails)вызовов QuerySet.__len__(), повторное использование кеша результатов.
  5. forЦикл перебирает уже заполненной кэш.

Всего этот код выполняет один или ноль запросов к базе данных. Единственная преднамеренная оптимизация - использование emailsпеременной. Использование QuerySet.exists()для ifили 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