Рубрики
Без рубрики

Оптимизация запросов Django

Вы когда-нибудь спрашивали себя, это лучший способ сделать этот запрос? или есть ли более эффективный … Теги с Django, Python, базой данных, WebDev.

Вы когда-нибудь спрашивали себя, это лучший способ сделать этот запрос? Или есть ли более эффективный способ?

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

Некоторые из этого времена Хитрость состоит в том, чтобы использовать доступные варианты 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”