django new orm
В этой статье мы поделимся с вами несколькими советами по оптимизации запросов к базе данных.
Для работы мы будем использовать следующие модели:
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField(blank=True)
likes = models.IntegerField(blank=True, default=0)
authors = models.ManyToManyField(Author, blank=True)
class Meta:
default_related_name = 'entries'
def __str__(self):
return self.headline Разработчик Django должен разбираться в оценке и кэшировании QuerySet для оптимизации обращений к базе данных. Более подробно можете почитать об этом здесь.
Используйте get(), если вы знаете, что существует только один объект, соответствующий вашему запросу. Если ни один элемент не соответствует запросу, то get() вызовет исключение DoesNotExist. Если запросу соответствует несколько объектов, то get() вызовет исключение MultipleObjectsReturned. Используйте get() следующим образом:
try:
one_entry = Entry.objects.get(blog=2000)
except Entry.DoesNotExist:
# запросу не соответствует ни один элемент.
pass
except Entry.MultipleObjectsReturned:
# запросу соответствует несколько элементов.
pass
else:
# запросу соответствует только один элемент.
print(one_entry) Используйте django-debug-toolbar и QuerySet.explain(), чтобы определить эффективность вашего кода. Разберитесь с django.db.connection, который записывает запросы, сделанные с текущим подключением. Ниже приведен код декоратора отладчика запросов для получения количества запросов в функции — вы можете использовать его, чтобы проверить эффективность вашего запроса. Есть также очень хороший пакет django-silk, которым тоже можно пользоваться.
from django.db import connection, reset_queries
import time
import functools
def query_debugger(func):
@functools.wraps(func)
def inner_func(*args, **kwargs):
reset_queries()
start_queries = len(connection.queries)
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
end_queries = len(connection.queries)
print(f"Function : {func.__name__}")
print(f"Number of Queries : {end_queries - start_queries}")
print(f"Finished in : {(end - start):.2f}s")
return result
return inner_func QuerySet обычно кеширует свои результаты, когда происходит оценка, и для любых дальнейших операций с этим QuerySet он сначала проверяет кешированные результаты. Но когда вы используете iterator(), он не проверяет кеш и считывает результаты непосредственно из базы данных. Результаты не сохраняются в QuerySet.
Для QuerySet, возвращающего большое количество объектов, которые требуют много памяти для кеширования и к которому вам нужно получить доступ только один раз, вы можете использовать iterator().
В следующем коде мы извлечем все записи из базы данных и загрузим в память, а затем проитерируемся по ним.
q = Entry.objects.all()
for each in q:
do_something(each) Когда мы используем iterator(), Django будет удерживать SQL-соединение открытым, читать каждую строку и вызывать do_something() перед чтением следующей строки.
q = Entry.objects.all().iterator()
for each in q:
do_something(each) Django при каждом запросе открывает новое соединение с базой данных и закрывает его, когда его запрос выполнен. За это поведение отвечает настройка CONN_MAX_AGE — значение по умолчанию равно 0. Но сколько секунд должно быть установлено соединение? Это зависит от трафика вашего сайта — чем больше трафика, тем больше секунд требуется для сохранения соединения. Мы бы рекомендовали установить относительно небольшое значение, например 60.
В Django select_related() и prefetch_related() предназначены для снижения количества запросов к базе данных. Более подробно о них вы можете прочитать здесь.
# Так делать не стоит.
for entry in Entry.objects.all():
entry.likes += 1
entry.save()
# Лучше так.
Entry.objects.update(likes=F('likes') + 1) # Так делать не стоит.
most_liked = 0
for entry in Entry.objects.all():
if entry.likes > most_liked:
most_liked = entry.likes
# Лучше так.
most_liked = Entry.objects.all().aggregate(Max('likes'))['likes__max'] Django ORM автоматически извлекает и кеширует внешние ключи, поэтому используйте их вместо ненужных запросов к базе данных.
# Так делать не стоит. Требуется обращение к базе данных.
blog_id = Entry.objects.get(id=200).blog.id
# Лучше так. Внешний ключ уже кеширован, поэтому обращение к базе данных не требуется.
blog_id = Entry.objects.get(id=200).blog_id
# Или так. Обращение к базе данных так же не требуется.
blog_id = Entry.objects.select_related('blog').get(id=200).blog.id Сортировка не бесплатная — каждое поле, которое должно быть отсортировано — это операция, которую должна выполнить база данных. Если у модели есть сортировка по умолчанию (Meta.ordering) и она вам не нужна, удалите ее в QuerySet, вызвав order_by() без параметров. Добавление индекса в вашу базу данных может повысить производительность сортировки.
Если вам не нужно содержимое QuerySet, используйте count() и exists().
# Так делать не стоит. count = len(Entry.objects.all()) # Лучше так. count = Entry.objects.count() # Так делать не стоит. qs = Entry.objects.all() if qs: pass # Лучше так. qs = Entry.objects.exists() if qs: pass
author1 = Author(name='author1') author2 = Author(name='author2') author3 = Author(name='author3') entry = Entry.objects.get(id=1) # Так делать не стоит. entry.authors.add(author1) entry.authors.add(author2) entry.authors.add(author3) # Лучше так. entry.authors.add(author1, author2, author3)
Если вы хотите удалить или обновить сразу несколько экземпляров модели, используйте соответственно delete() и update().
# Так делать не стоит. Элементы удаляются по одному.
for entry in Entry.objects.all():
entry.delete()
# Лучше так. Удаляются все сразу.
Entry.objects.all().delete()
# Так делать не стоит.
for entry in Entry.objects.all():
entry.likes += 1
entry.save()
# Лучше так.
Entry.objects.update(likes=F('likes')+1) # Так делать не стоит.
for i in range(20):
Blog.objects.create(name="blog"+str(i), headline='tagline'+str(i))
# Лучше так.
blogs = []
for i in range(20):
blogs.append(Blog(name="blog"+str(i), headline='tagline'+str(i)))
Blog.objects.bulk_create(blogs) Если вам нужны определенные поля в результатах QuerySet и вы хотите получить результаты в списке, кортеже или словарях, используйте values() и values_list().
Если вам нужны определенные поля в результатах QuerySet вы хотите получить объекты модели в QuerySet вместо списка, кортежа или словарей, используйте defer() и only().
Если вы ищете работу на Python, на Talanto.work сейчас есть варианты для junior, middle, senior…
Рынок Python-вакансий большой, но удобным его назвать сложно. Формально вакансий много, однако в реальном поиске…
Срезы в Python — это больше, чем a[2:7]. В статье — быстрый разбор полезных приёмов:…
В этой статье мы рассмотрим возведение в степень в Python с помощью различных методов, от…
Дубликаты в списке Python — частая проблема. В статье показано, как быстро убрать повторы, в…
Услуга виртуального хостинга на SpaceWeb позволяет арендовать часть сервера (shared-сервера) и разместить на нём свой…