A. 쓸데 없이 데이터베이스에 Select 쿼리를 날리기 때문이다.
간단히 다음과 같은 장고 모델이 있다고 하자.
전체 코드는 DRF_CRUD 예제에서 확인할 수 있다.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Beverage(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.IntegerField()
is_available = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Beverage 모델의 category 필드는 외래키가 걸려 있다. bevarage.category.name 이런 식으로 해당 beverage에 연결된 category의 필드에 접근이 가능하다. 데이터를 한 두개 입력하자.
로그 설정에 아래처럼 추가하자. 그래야 데이터베이스 쿼리문을 확인할 수 있다. 참고: Django Logging
LOGGING = {
'version': 1,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
}
}
}
확인하는 방법
장고 셸에 들어가자.
아래 명령어를 입력하면 장고 셸로 들어갈 수 있다.
$ python manage.py shell
아래 코드를 장고 셸에서 실행하자.
from app.models import Beverage
beverage_qs = Beverage.objects.all()
for beverage in beverage_qs:
print(beverage.category_id)
for beverage in beverage_qs:
print(beverage.category.id)
그럼 아래와 같은 결과를 얻을 수 있다.
>>> from app.models import Beverage
>>> beverage_qs = Beverage.objects.all()
>>> for beverage in beverage_qs:
... print(beverage.category_id)
...
(0.001)
SELECT VERSION(),
@@sql_mode,
@@default_storage_engine,
@@sql_auto_is_null,
@@lower_case_table_names,
CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
; args=None
(0.001) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.001) SELECT `app_beverage`.`id`, `app_beverage`.`name`, `app_beverage`.`category_id`, `app_beverage`.`price`, `app_beverage`.`is_available`, `app_beverage`.`created_at`, `app_beverage`.`updated_at` FROM `app_beverage`; args=()
1
1
>>> for beverage in beverage_qs:
... print(beverage.category.id)
...
(0.001) SELECT `app_category`.`id`, `app_category`.`name`, `app_category`.`created_at`, `app_category`.`updated_at` FROM `app_category` WHERE `app_category`.`id` = 1 LIMIT 21; args=(1,)
1
(0.001) SELECT `app_category`.`id`, `app_category`.`name`, `app_category`.`created_at`, `app_category`.`updated_at` FROM `app_category` WHERE `app_category`.`id` = 1 LIMIT 21; args=(1,)
1
category.id로 할 때마다 SELECT가 호출되는 것을 확인할 수 있다.
아래처럼 확인해도 알 수 있다.
from app.models import Beverage
beverage = Beverage.objects.get(pk=1)
print(beverage.category.id)
실제 해보면 아래처럼 된다. SELECT 쿼리를 추가로 날려서 category의 id를 얻는 것을 확인할 수 있다.
>>> from app.models import Beverage
>>> beverage = Beverage.objects.get(pk=1)
(0.001)
SELECT VERSION(),
@@sql_mode,
@@default_storage_engine,
@@sql_auto_is_null,
@@lower_case_table_names,
CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
; args=None
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.001) SELECT `app_beverage`.`id`, `app_beverage`.`name`, `app_beverage`.`category_id`, `app_beverage`.`price`, `app_beverage`.`is_available`, `app_beverage`.`created_at`, `app_beverage`.`updated_at` FROM `app_beverage` WHERE `app_beverage`.`id` = 1 LIMIT 21; args=(1,)
>>> print(beverage.category.id)
(0.001) SELECT `app_category`.`id`, `app_category`.`name`, `app_category`.`created_at`, `app_category`.`updated_at` FROM `app_category` WHERE `app_category`.`id` = 1 LIMIT 21; args=(1,)
1
이번에는 category_id로 해보자.
from app.models import Beverage
beverage = Beverage.objects.get(pk=1)
print(beverage.category_id)
print(beverage.__dict__)
아래와 같은 결과를 얻을 수 있다.
>>> from app.models import Beverage
>>> beverage = Beverage.objects.get(pk=1)
(0.001)
SELECT VERSION(),
@@sql_mode,
@@default_storage_engine,
@@sql_auto_is_null,
@@lower_case_table_names,
CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
; args=None
(0.001) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.001) SELECT `app_beverage`.`id`, `app_beverage`.`name`, `app_beverage`.`category_id`, `app_beverage`.`price`, `app_beverage`.`is_available`, `app_beverage`.`created_at`, `app_beverage`.`updated_at` FROM `app_beverage` WHERE `app_beverage`.`id` = 1 LIMIT 21; args=(1,)
>>> print(beverage.category_id)
1
>>> print(beverage.__dict__)
{'_state': <django.db.models.base.ModelState object at 0x7f804802a9a0>, 'id': 1, 'name': 'Americano', 'category_id': 1, 'price': 3500, 'is_available': True, 'created_at': datetime.datetime(2022, 10, 25, 14, 46, 13, 74719, tzinfo=<UTC>), 'updated_at': datetime.datetime(2022, 10, 25, 14, 46, 13, 74742, tzinfo=<UTC>)}
SELECT 쿼리가 없다. 그 이유는 beverage 객체에 beverage_id 속성이 이미 있기 때문이다. ForeignKey의 경우에는 다른 필드와 다르게 속성이 field_id로 그래서 category_id로 된 것을 확인할 수 있다.
결론
ForeignKey 필드는 무조건 field_id를 쓰자. field.id를 쓰면 절대 안 된다. 정말 쓸데 없이 SELECT 쿼리만 한번 더 날리기 때문이다.
만약 위 예에서 for문을 도는 횟수가 1000번이라면 category.id 때문에 추가로 데이터베이스에 SELECT 쿼리가 1000번 날라가게 되니까.