Вы когда-нибудь спрашивали себя, это лучший способ сделать этот запрос? Или есть ли более эффективный способ?
Когда у нас есть проект с одним или несколькими рекуррентными или длинными блокирующими запросами, нам нужно учитывать, что этот запрос может замедлить наше приложение или даже сделать сбой базы данных.
Некоторые из этого времена Хитрость состоит в том, чтобы использовать доступные варианты ORM и в несколько раз лучший способ – написать индивидуальный запрос SQL, но как узнать, что лучше всего в каждом случае?
В этой статье я покажу вам ряд лучших практик, рекомендованных Django Documentation, и по опыту, так что у вас будет представление о некоторых вариантах, чтобы оптимизировать свой код!
Прежде чем решить, что вам нужно использовать, вы должны знать, что вы можете оптимизировать запросы для скорости или для памяти или обоих, в зависимости от ваших требований. Но иногда оптимизация для одного повлияет на другую. Кроме того, задачи, которые обрабатываются базой данных, могут не иметь тех же стоимость ресурсов, чем те, которые сделаны Python Process. Вам решать, какие ваши приоритеты есть и где баланс должен лгать, чтобы вы могли улучшить ресурсы сервера в соответствии с ним.
Имея следующую схему
class User(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.TextField() email = models.TextField() age = models.PositiveSmallIntegerField() team = models.ForeignKey(Team, on_delete=models.PROTECT, related_name='users', null=True) class Team(models.Model): location = models.TextField() name = models.TextField()
Сначала вы не должны понимать, что запросы ленивы, но что это значит?
Это означает, что акт создания или объявления запроса не включает никакой активности базы данных. Таким образом, вы можете добавить фильтры и условия линии после строки, а запрос не будет выполняться до тех пор, пока вы на самом деле не просите информацию, например:
user_query = User.objects.filter(name="Ana") # Filter the users by the name "Ana" user_query = user_query.filter(age__lte=50) # Then get only the users where the age is less than 50 user_query = user_query.exclude(email__isnull=True) # And exclude the users that doesn't have an email print(user_query)
Теперь в этих строках кода база данных получает только один удар в линейной печати (user_query), которая очень полезна, когда вы хотите добавить много условий до выполнения, но будьте осторожны, потому что есть некоторые другие методы, которые могут ударить в базу данных Сразу, как:
# Iteration for user in User.objects.all(): print(user.name) # Slicing User.objects.all()[:10]
# Pickling or Caching pickle_str = pickle.dumps(user_queryset) user_queryset = pickle.loads(pickle_str) len(user_queryset._result_cache) # Methods like repr(), len(), list(), bool() user_list = list(User.objects.all()) # or a boolean condition like: if User.objects.filter(name="Ana"): print("There is at least one user who's name is Ana")
Это также означает, что вам нужно отличить, какие запросы кэшируются, поэтому он не будет ударить в базу данных, и что не будет, если запрос повторяется, например:
# Create the query filtering users with email myemail@gmail.com users = User.objects.get(email="myemail@gmail.com") users.name # Hit the database and retrieve the name value users.name # cached version, no database access # Create the query filtering teams named tigers team = Team.objects.get(name="tigers") team.users.all() # query performed team.users.all() # query performed again
Таким образом, если вам нужно повторить огромный набор данных, а затем выполнить некоторые действия со значениями. Посваримый раствор – использовать итератор ().
for user in User.objects.exclude(email__isnull=True).iterator(): print(user.name)
Он ударит в базу данных, просто когда-то вытекает подходящие строки, но используйте ITERATOR () с осторожностью и убедиться, что ваш код организован, чтобы избежать повторной оценки того же огромного запроса.
Способ избежать избытка использования памяти, когда наборный набор огромен, – это выполнить логическое состояние до заполнения всего набора данных.
user_queryset = User.objects.all() # The first hit to the database confirms if at least one object exists. if user_queryset.exists(): # Another database hit to start fetching the rows in batches. for user in user_queryset.iterator(): print(user.name)
Но не злоупотребляйте о существующих (), подсчитайте методы, потому что каждый раз вы называете их новым ударом.
Мы также сможем получить все сразу, если мы знаем, что нам, безусловно, вам нужно, чтобы сделать это, мы можем использовать Prefetch_related () или select_related (), поэтому мы можем получить связанные поля в других объектах и правильный объект сразу.
Team.objects.all().prefetch_related('users')
Это вернет запрос, который автоматически извлекает, в одном пакете, связанных объектах для каждого из указанных поисков.
Теперь у вас есть select_related (), который возвращает запрос, который будет искать отношения внешних ключей, выбирая дополнительные данные с соответствующими объектами, когда она выполняет его запрос. Это усилитель производительности, который приводит к одному более сложному запросу, но означает, что более позднее использование отношений с внешним ключом не потребует запросов баз данных, например, если вам нужен весь пользователь для команды.
# Hits the database team_queryset = Team.objects.get(name="tigers") # Hits the database again to get the related User objects user = team_queryset.user
Так что вместо этого вы можете сделать это:
# Hits the database team_queryset = Team.objects.select_related("user").get(name="tigers") # The second line doesn't hit the database, because team_queryset.user has been previously prepopulated user = team_queryset.user
Еще один способ сделать запрос быстрее – получить отдельные объекты, используя уникальную, индексируемую колонку. Как и в любом другом ORM, запрос будет быстрее из-за базового индекса базы данных. Кроме того, запрос проходит гораздо медленнее, когда есть несколько объектов, соответствующих фильтру; Наличие уникального ограничения на столбец гарантирует, что никогда не произойдет.
user_queryset = User.objects.get(id=10)
Не заказывайте результаты, если вам все равно. Иногда Django заказывает набор данных, даже если мы не просим этого, но мы можем отключить его на запросе, вызывая order_by () без параметров.
user_queryset = User.objects.all().order_by()
Используйте отделку () и только (), если вам нужны определенные поля только на наборе данных, чтобы избежать загрузки всех полей. Обратите внимание, что если вы используете их позже, ORM должен будет пойти и получить их в отдельном запросе, что делает это пессимизацию, если вы используете его неуместно.
# This query retrieves all of the rows on the dataset but only the "name" field user_queryset = User.objects.all().only("name")
Регистрация всех SQL-запросов
Довольно полезный инструмент – это регистратор ORM, это позволит вам проверить все запросы, а также получить время выполнения. Чтобы включить это, вам нужно добавить несколько вещей в вашу конфигурацию журнала в настройках .py. Во-первых, вам понадобится обработчик. Пример ниже журналирует все на уровне отладки на консоль.
LOGGING = { 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, }
Далее вам нужно будет добавить регистратор, который входит в этот обработчик:
LOGGING = { 'loggers': { 'django.db.backends': { 'level': 'DEBUG', 'handlers': ['console'], }, }, }
Как только вышеприведенное настроено, вы должны увидеть поток запросов SQL в вашем консольном выходе каждый раз, когда вы попадаете в базу данных. Вывод
Там нет идеального запроса или общего ответа на использование ORM. Вы должны сначала стремиться сделать ваш код ясным, а затем работать над оптимизацией его и принять решение о том, что некоторые практики могут ускорить процессы базы данных, но тратить много памяти, чтобы быть осторожным и анализировать, что лучшее для вашего приложения и ваших ресурсов Отказ
Первоначально опубликовано в https://icodemag.com/djang-queries-optimization/
Оригинал: “https://dev.to/valerybriz/django-queries-optimization-10ji”