Django ORM (Object Relational Mapping) является одной из самых мощных особенностей Django. Это позволяет нам взаимодействовать с базой данных, используя код Python, а не SQL.
Для демонстрации опишу такую модель:
from django.db import models class Blog(models.Model): name = models.CharField(max_length=250) url = models.URLField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=250) def __str__(self): return self.name class Post(models.Model): title = models.CharField(max_length=250) content = models.TextField() published = models.BooleanField(default=True) blog = models.ForeignKey(Blog, on_delete=models.CASCADE) authors = models.ManyToManyField(Author, related_name="posts")
Я буду использовать django-extentions, чтобы получить полезную информацию с помощю с
python manage.py shell_plus --print-sql
И так начнем:
>>> post = Post.objects.all() >>> post SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id" FROM "blog_post" LIMIT 21 Execution time: 0.000172s [Database: default] <QuerySet [<Post: Post object (1)>]>
1. Используем кэшированные ForeignKey ids
>>> Post.objects.first().blog.id SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id" FROM "blog_post" ORDER BY "blog_post"."id" ASC LIMIT 1 Execution time: 0.000225s [Database: default] SELECT "blog_blog"."id", "blog_blog"."name", "blog_blog"."url" FROM "blog_blog" WHERE "blog_blog"."id" = 1 LIMIT 21 Execution time: 0.000144s [Database: default] 1
А так получаем 1 запрос в БД:
>>> Post.objects.first().blog_id SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id" FROM "blog_post" ORDER BY "blog_post"."id" ASC LIMIT 1 Execution time: 0.000155s [Database: default] 1
2. OneToMany Relations
Если мы используем OneToMany отношения мы используем ForeignKey поля и запрос выглядит примерно так:
>>> post = Post.objects.get(id=1) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id" FROM "blog_post" WHERE "blog_post"."id" = 1 LIMIT 21 Execution time: 0.000161s [Database: default]
И если мы хотим получить доступ к объекту блога из объекта поста, мы можем сделать:
>>> post.blog SELECT "blog_blog"."id", "blog_blog"."name", "blog_blog"."url" FROM "blog_blog" WHERE "blog_blog"."id" = 1 LIMIT 21 Execution time: 0.000211s [Database: default] <Blog: Django tutorials>
Тем не менее, это вызвало новый запрос, чтобы получить информацию из блога. Так что используйте select_related, чтобы избежать этого. Чтобы использовать его, мы можем обновить наш оригинальный запрос:
>>> post = Post.objects.select_related("blog").get(id=1) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id", "blog_blog"."id", "blog_blog"."name", "blog_blog"."url" FROM "blog_post" INNER JOIN "blog_blog" ON ("blog_post"."blog_id" = "blog_blog"."id") WHERE "blog_post"."id" = 1 LIMIT 21 Execution time: 0.000159s [Database: default]
Обратите внимание, что Django использует JOIN сейчас! И время выполнения запроса меньше, чем раньше. Кроме того, теперь post.blog будет кэширован!
>>> post.blog <Blog: Django tutorials>
select_related так же работает с QurySets:
>>> posts = Post.objects.select_related("blog").all() >>> for post in posts: ... post.blog ... SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id", "blog_blog"."id", "blog_blog"."name", "blog_blog"."url" FROM "blog_post" INNER JOIN "blog_blog" ON ("blog_post"."blog_id" = "blog_blog"."id") Execution time: 0.000241s [Database: default] <Blog: Django tutorials>
3. ManyToMany Relations:
Чтобы получить авторов постов мы используем что-то вроде этого:
>>> for post in Post.objects.all(): ... post.authors.all() ... SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id" FROM "blog_post" Execution time: 0.000242s [Database: default] SELECT "blog_author"."id", "blog_author"."name" FROM "blog_author" INNER JOIN "blog_post_authors" ON ("blog_author"."id" = "blog_post_authors"."author_id") WHERE "blog_post_authors"."post_id" = 1 LIMIT 21 Execution time: 0.000125s [Database: default] <QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>, <Author: Guido van Rossum>]> SELECT "blog_author"."id", "blog_author"."name" FROM "blog_author" INNER JOIN "blog_post_authors" ON ("blog_author"."id" = "blog_post_authors"."author_id") WHERE "blog_post_authors"."post_id" = 2 LIMIT 21 Execution time: 0.000109s [Database: default] <QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>]>
Похоже, мы получили запрос для каждого объекта поста. По этому, мы должны использовать prefetch_related. Это похоже на select_related но используется с ManyToMany Fields:
>>> for post in Post.objects.prefetch_related("authors").all(): ... post.authors.all() ... SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."blog_id" FROM "blog_post" Execution time: 0.000300s [Database: default] SELECT ("blog_post_authors"."post_id") AS "_prefetch_related_val_post_id", "blog_author"."id", "blog_author"."name" FROM "blog_author" INNER JOIN "blog_post_authors" ON ("blog_author"."id" = "blog_post_authors"."author_id") WHERE "blog_post_authors"."post_id" IN (1, 2) Execution time: 0.000379s [Database: default] <QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>, <Author: Guido van Rossum>]> <QuerySet [<Author: Dmytro Parfeniuk>, <Author: Will Vincent>]>
Что только что произошло??? Мы сократили количество запросов с 2 до 1, чтобы получить 2 QuerySet-a!
4. Prefetch object
prefetch_related достаточно для большинства случаев, но это не всегда помогает избежать дополнительных запросовю К примеру, если мы используем фильтрацию Django не может использовать наши кэшированные posts, так как они не были отфильтрованы, когда они были запрошены в первом запросе. И мы будем получим:
>>> authors = Author.objects.prefetch_related("posts").all() >>> for author in authors: ... print(author.posts.filter(published=True)) ... SELECT "blog_author"."id", "blog_author"."name" FROM "blog_author" Execution time: 0.000580s [Database: default] SELECT ("blog_post_authors"."author_id") AS "_prefetch_related_val_author_id", "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."published", "blog_post"."blog_id" FROM "blog_post" INNER JOIN "blog_post_authors" ON ("blog_post"."id" = "blog_post_authors"."post_id") WHERE "blog_post_authors"."author_id" IN (1, 2, 3) Execution time: 0.000759s [Database: default] SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."published", "blog_post"."blog_id" FROM "blog_post" INNER JOIN "blog_post_authors" ON ("blog_post"."id" = "blog_post_authors"."post_id") WHERE ("blog_post_authors"."author_id" = 1 AND "blog_post"."published" = 1) LIMIT 21 Execution time: 0.000299s [Database: default] <QuerySet [<Post: Post object (1)>, <Post: Post object (2)>]> SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."published", "blog_post"."blog_id" FROM "blog_post" INNER JOIN "blog_post_authors" ON ("blog_post"."id" = "blog_post_authors"."post_id") WHERE ("blog_post_authors"."author_id" = 2 AND "blog_post"."published" = 1) LIMIT 21 Execution time: 0.000336s [Database: default] <QuerySet [<Post: Post object (1)>, <Post: Post object (2)>]> SELECT "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."published", "blog_post"."blog_id" FROM "blog_post" INNER JOIN "blog_post_authors" ON ("blog_post"."id" = "blog_post_authors"."post_id") WHERE ("blog_post_authors"."author_id" = 3 AND "blog_post"."published" = 1) LIMIT 21 Execution time: 0.000412s [Database: default] <QuerySet [<Post: Post object (1)>]>
То есть, мы использовали prefetch_related, чтобы уменьшить количество запросов, но мы фактически увеличили его. Чтобы этого избежать, мы можем настроить запрос с помощью объекта Prefetch:
>>> authors = Author.objects.prefetch_related( ... Prefetch( ... "posts", ... queryset=Post.objects.filter(published=True), ... to_attr="published_posts", ... ) ... ) >>> for author in authors: ... print(author.published_posts) ... SELECT "blog_author"."id", "blog_author"."name" FROM "blog_author" Execution time: 0.000183s [Database: default] SELECT ("blog_post_authors"."author_id") AS "_prefetch_related_val_author_id", "blog_post"."id", "blog_post"."title", "blog_post"."content", "blog_post"."published", "blog_post"."blog_id" FROM "blog_post" INNER JOIN "blog_post_authors" ON ("blog_post"."id" = "blog_post_authors"."post_id") WHERE ("blog_post"."published" = 1 AND "blog_post_authors"."author_id" IN (1, 2, 3)) Execution time: 0.000404s [Database: default] [<Post: Post object (1)>, <Post: Post object (2)>] [<Post: Post object (1)>, <Post: Post object (2)>] [<Post: Post object (1)>]
Мы использовали определенный запрос для получения постов через параметр запроса и сохранили отфильтрованные сообщения в новом атрибуте. Как мы видим, теперь у нас есть только 2 запроса в базу данных.
ссылка на оригинал статьи https://habr.com/ru/post/503526/