select_related, prefetch_related는 하나의 쿼리셋을 가져올 때 연관되어 있는 objects들을 미리 불러오게(Eager Loading)하는 함수이다. JOIN문을 사용하기 때문에 호출되는 SQL문이 복잡해질 수 있지만, 이렇게 불러온 데이터들은앞서 알아본 result_cache 라는 부분에 cache 되기 때문에 결과적으로 중복 호출을 방지할 수 있다. 두 함수 모두 DB에 엑세스(connection)하는 횟수를 줄여주므로 Performance를 향상시킬 수 있다. 하지만 두 함수의 SQL 문이 호출되는 방식에는 차이가 있다.
select_related
: JOIN을 통해 데이터를 즉시 가져오는 방법(Eager Loading) - SQL단계에서의 JOINprefetch_related
: 추가 쿼리를 통해 데이터를 즉시 가져오는 방법(추가 쿼리 발생) - JOIN은 파이썬 level에서 이루어짐1 : 1 관계
= OneToOneField
1 : N 관계
에서 ForeignKey
를 물고 있는 N
이 사용(=정참조)OneToOneField
인 경우 필드 이름을 지정하는 대신 models.py에서 related_name
속성을 사용해야 한다.from django.db import models
class City(models.Model):
# ...
pass
class Person(models.Model):
# ...
hometown = models.ForeignKey(
City,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
class Book(models.Model):
# ...
author = models.ForeignKey(Person, on_delete=models.CASCADE)
위와 같은 모델이 있을 경우를 생각해보자
City와 Perosn 관계
- Person이 City를 (정)참조하고 있다
- City와 Person의 관계는 1:N 이다
- N에 해당하는 Person 모델에 City를 FK로 물고 있는 필드가 있다
- Person -> City : 정참조
- City -> Person : 역참조
Book 객체에서 hometown의 정보를 가져오기 위한 2가지 방법이 있다
select_relate()를 안쓰는 방법
b = Book.objects.get(id=4) # Hits the database.
p = b.author # Hits the database.
c = p.hometown # Hits the database.
이 경우 DB에 총 3번 요청하기 때문에 비효율적이다.
select_relate()를 쓰는 방법
b = Book.objects.select_related('author_hometown').get(id=4) # Hits the database.
p = b.author
# Doesn't hit the database, because b.author has been prepopulated in the previous query.
c = p.hometown
이 경우 select_related()로 정보를 이미 불러 두었기 때문에 총 한 번만 접근하면 된다
select_related()
는 객체의 다른 모든 쿼리 세트와 함께 사용할 수 있다for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
# Without select_related(), this would make a database query for each
# loop iteration in order to fetch the related blog for each entry.
blogs.add(e.blog)
filter()
와 select_related()
의 연결 순서는 상관없다Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())
select_related('foo', 'bar')
select_related('foo').select_related('bar')
1 : N 관계
에서 1
이 사용M : N 관계
>>> post = Post.objects.select_related('category').all()
>>> for p in post:
... p.category.name
...
SELECT "mysite2_post"."id",
"mysite2_post"."category_id",
"mysite2_post"."title",
"mysite2_post"."content",
"mysite2_post"."created_at",
"mysite2_post"."updated_at",
"mysite2_category"."id",
"mysite2_category"."blog_id",
"mysite2_category"."name"
FROM "mysite2_post"
LEFT OUTER JOIN "mysite2_category"
ON ("mysite2_post"."category_id" = "mysite2_category"."id")
Execution time: 0.007924s [Database: default]
'programming category'
'programming category'
'programming category'
'programming category'
'programming category'
>>>
python
category = Category.objects.prefetch_related('post_set').get(name='django')
SELECT "mysite2_category"."id",
"mysite2_category"."blog_id",
"mysite2_category"."name"
FROM "mysite2_category"
WHERE "mysite2_category"."name" = 'django'
LIMIT 21
Execution time: 0.000285s [Database: default]
SELECT "mysite2_post"."id",
"mysite2_post"."category_id",
"mysite2_post"."title",
"mysite2_post"."content",
"mysite2_post"."created_at",
"mysite2_post"."updated_at"
FROM "mysite2_post"
WHERE "mysite2_post"."category_id" IN (6)
Execution time: 0.000296s [Database: default]
for c in category.post_set.all():
... c.title
...
'django 1'
'django 2'
'django 3'