시작하기 전에
안녕하세요 똑똑한 개발자에서 백엔드 개발을 하고 있는 jujun입니다.
django orm 은 디폴트로 해당 모델의 모든 컬럼을 불러옵니다.
하지만 only 기능을 사용한다면 필요한 항목만 불러올 수 있게 됩니다.
이는 불필요한 데이터를 불러오지 않게하여 컴퓨터 자원을 보다 효율적으로 사용할수 있습니다.
하지만 잘못 사용하면 lazy lading 에 의해 불필요한 쿼리를 발생시킵니다.
자세히 알아보겠습니다.
.
본론
예제코드
아래와 같은 모델이 있습니다.
# api.raw.models
class Party(models.Model):
country = models.ForeignKey("raw.Country", verbose_name="국가", on_delete=models.SET_NULL ,null=True)
owner = models.ForeignKey("user.User", verbose_name="소유자", on_delete=models.SET_NULL ,null=True)
name = models.CharField("정당 이름", max_length=50)
description = models.TextField("설명", blank = True, null = True)
image = models.ImageField("사진", upload_to='raw/party/', blank = True, null = True)
color = models.CharField("정당 색", max_length=50)
class Meta:
verbose_name = '정당'
verbose_name_plural = verbose_name
ordering = ['country', 'name']
def __str__(self):
return self.name
only() 기능
django orm 은 기본적으로 모델에 있는 모든 항목들을 DB로부터 불러옵니다.
>>> from api.raw.models import Party
>>> p_default = Party.objects.get(id=1)
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_party"."description", "raw_party"."image", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."id" = 1 LIMIT 21; args=(1,)
>>> p_default.name
'국민'
django shell 에서 Party 모델에서 id 값이 1인 인스턴스를 불러왔습니다. 0.001초가 걸렸고 Party 에 있는 id, country_id, 등등 모든 항목들을 가져왔습니다.
only를 통해 name 만 가져오겠습니다.
>>> from api.raw.models import Party
>>> p_only = Party.objects.only('name').get(id=2)
(0.001) SELECT "raw_party"."id", "raw_party"."name" FROM "raw_party" WHERE "raw_party"."id" = 2 LIMIT 21; args=(2,)
>>> p_only.name
'더불어'
0.001가 걸렸고 모든 컬럼이 아닌 id와 name 항목만 가져왔습니다.
update
해당 인스턴스를 업데이트 하겠습니다.
>>> p_default.name ='국민의힘'
>>> p_default.save()
(0.009) UPDATE "raw_party" SET "country_id" = 1, "owner_id" = 1, "name" = '국민의힘', "description" = '', "image" = '', "color" = 'red' WHERE "raw_party"."id" = 1; args=(1, 1, '국민의힘', '', '', 'red', 1)
기본 orm 으로 불러왔던 모든 데이터가 DB에 반영되었습니다. 실행 시간은 0.009초입니다.
>>> p_only.name = '더불어민주당'
>>> p_only.save()
(0.003) UPDATE "raw_party" SET "name" = '더불어민주당' WHERE "raw_party"."id" = 2; args=('더불어민주당', 2)
>>>
반면 only를 통해 불러온 인스턴스는 name 만 반영하였습니다. 실행시간은 0.003 초입니다.
Filter 에서 사용
>>> for party in Party.objects.filter(country_id=1).only('name'):
... party.name
...
(0.001) SELECT "raw_party"."id", "raw_party"."name" FROM "raw_party" WHERE "raw_party"."country_id" = 1 ORDER BY "raw_party"."country_id" ASC, "raw_party"."name" ASC; args=(1,)
'국민의힘'
'더불어민주당'
Select_related 에서 사용
>>> q = Party.objects.only('name','owner__email', 'country__name').select_related('owner', 'country').filter(country_id=1)
>>> for party in q:
... party.name
... party.country.name
... party.owner.email
...
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_country"."id", "raw_country"."name", "user_user"."id", "user_user"."email" FROM "raw_party" INNER JOIN "raw_country" ON ("raw_party"."country_id" = "raw_country"."id") LEFT OUTER JOIN "user_user" ON ("raw_party"."owner_id" = "user_user"."id") WHERE "raw_party"."country_id" = 1 ORDER BY "raw_party"."country_id" ASC, "raw_party"."name" ASC; args=(1,)
'국민의힘'
'Korea'
'admin@admin.com'
'더불어민주당'
'Korea'
'user1@user.com'
>>>
Object로 검색하기
>>> u = User.objects.only('nickname', 'email').get(id=1)
(0.021) SELECT "user_user"."id", "user_user"."email", "user_user"."nickname" FROM "user_user" WHERE "user_user"."id" = 1 LIMIT 21; args=(1,)
>>> Party.objects.get(owner=u)
(0.005) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_party"."description", "raw_party"."image", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."owner_id" = 1 LIMIT 21; args=(1,)
<Party: 국민의힘>
>>>
해당 모델 인스턴스는 생기지만 DB에서 데이터를 가져오지 않는 것으로 보입니다.
get_or_create
>>> party = Party.objects.get(id = 1) # 필요한 데이터를 불러오는 중
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_party"."description", "raw_party"."image", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."id" = 1 LIMIT 21; args=(1,)
>>> upp = RegionUpp.objects.get(id = 1)
(0.005) SELECT "raw_regionupp"."id", "raw_regionupp"."name", "raw_regionupp"."country_id" FROM "raw_regionupp" WHERE "raw_regionupp"."id" = 1 LIMIT 21; args=(1,)
>>> party_usa = Party.objects.get(id=3)
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_party"."description", "raw_party"."image", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."id" = 3 LIMIT 21; args=(3,)
>>>
>>> upp
<RegionUpp: 텍사스>
>>> party
<Party: 국민의힘>
>>> party_usa
<Party: Republican>
>>>
# get 으로 사용해봄.
>>> result_get , created = ResultRegionUpp.objects.only('total').get_or_create(region_upp = upp, party=party_usa)
(0.005) SELECT "result_resultregionupp"."id", "result_resultregionupp"."total" FROM "result_resultregionupp" WHERE ("result_resultregionupp"."party_id" = 3 AND "result_resultregionupp"."region_upp_id" = 1) LIMIT 21; args=(3, 1)
>>> created
False
>>>
>>> result_get.total
0
# only 외의 데이터를 호출하면 쿼리 발생
>>> result_get.party
(0.001) SELECT "result_resultregionupp"."id", "result_resultregionupp"."party_id" FROM "result_resultregionupp" WHERE "result_resultregionupp"."id" = 7 LIMIT 21; args=(7,)
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_party"."description", "raw_party"."image", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."id" = 3 LIMIT 21; args=(3,)
<Party: Republican>
>>>
# create 로 사용해봄.
>>> result_create, created = ResultRegionUpp.objects.only('total').get_or_create(region_upp=upp, party=party)
(0.003) SELECT "result_resultregionupp"."id", "result_resultregionupp"."total" FROM "result_resultregionupp" WHERE ("result_resultregionupp"."party_id" = 1 AND "result_resultregionupp"."region_upp_id" = 1) LIMIT 21; args=(1, 1)
(0.002) INSERT INTO "result_resultregionupp" ("region_upp_id", "party_id", "total") VALUES (1, 1, 0) RETURNING "result_resultregionupp"."id"; args=(1, 1, 0)
>>> created
True
>>> result_create.total
0
>>> result_create.party
<Party: 국민의힘>
get_or_create에서 사용해 보았습니다. get되는 경우는 only 가 잘 작동하지만 create된 경우에는 모든 항목이 불러옵니다. 아래는 ResultRegionUpp모델입니다. 혹시나 궁금할실까봐 가져왔습니다.
class ResultRegionUpp(models.Model):
region_upp = models.ForeignKey("raw.RegionUpp", verbose_name="상위지역", on_delete=models.SET_NULL,null=True, blank=True)
party = models.ForeignKey("raw.party", verbose_name="정당", on_delete=models.SET_NULL,null=True, blank=True)
total = models.PositiveIntegerField("물감수", default= 0)
class Meta:
verbose_name = "상위 지역별 물감 현황"
verbose_name_plural = verbose_name
ordering= ['region_upp', '-total']
def __str__(self):
return f'{self.region_upp}__{self.party}'
단점
>>> p_default = Party.objects.get(id=1)
(0.001) SELECT "raw_party"."id", "raw_party"."country_id", "raw_party"."owner_id", "raw_party"."name", "raw_party"."description", "raw_party"."image", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."id" = 1 LIMIT 21; args=(1,)
>>> p_default.color
'red'
>>>
>>> p_only = Party.objects.only('name').get(id=2)
(0.001) SELECT "raw_party"."id", "raw_party"."name" FROM "raw_party" WHERE "raw_party"."id" = 2 LIMIT 21; args=(2,)
>>> p_only.color
(0.001) SELECT "raw_party"."id", "raw_party"."color" FROM "raw_party" WHERE "raw_party"."id" = 2 LIMIT 21; args=(2,)
'blue'
미리 불러오지 않은 데이터를 호출하면 그제서야 DB에 가서 필요한 데이터를 가져옵니다. 이것은 불필요한 작업입니다.
마치는 말
모델이 커지면 커질 수록 데이터를 불러올 때 불필요한 데이터도 많아집니다. 그렇다고 여러 모델을 만들어 FK 연결하는 것은 어떤 데이터가 어디에 있는지 등 여러모로 머리를 복잡하게 만듭니다. only를 사용하여 보다 간단한 데이터 구조를 만드시길 바랍니다.