Model.objects.get()Model.objects.filter() 모두 장고와 연결된 데이터베이스에서 조건에 맞는 데이터를 리턴하고 싶을 때 사용한다. 그러나 두 메소드는 엄연한 차이가 있다. 

 

쿼리셋(QuerySet)

둘의 차이를 이해하기 위해서는 쿼리셋이 무엇인지를 먼저 알아봐야 한다. 

쿼리셋(Queryset)이란 장고와 연결된 데이터베이스에 저장된 객체들의 모임을 의미한다. 쿼리셋은 SQL문으로 치면 SELECT문과 같다. 여기에 WHERE, LIMIT 등의 여러 필터를 사용해서 원하는 데이터만 포함한 쿼리셋을 만들 수 있다. 

+ 앞으로 '쿼리를 날린다'는 표현을 사용할 건데, '쿼리를 날린다' = '데이터베이스에 연결한다' 라고 보면 된다. 

 

쿼리셋과 데이터베이스 접근은 다르다. (Querysets are lazy)

기본적으로 쿼리셋은 메모리의 일종인 캐시(cache)를 사용해서 데이터베이스에 접근을 최소화 하려고 한다. 따라서 만약 어떤 메소드가 쿼리셋을 리턴하고 그 리턴된 쿼리셋이 캐시 메모리를 포함한다면, 나중에 같은 정보가 필요할 때 기존에 리턴된 쿼리셋을 사용할 수 있다. 

 

쿼리셋이 만들어지거나 쿼리셋에 추가적인 필터 메소드를 적용한다고 해서 항상 데이터베이스에 쿼리를 날리는 것이 아니다. 쿼리셋이 만들어진 이후, 쿼리셋을 계산(evaluate)하는 메소드가 실행되면 그 때 쿼리를 날리게 된다. 

 

쿼리셋을 계산하는 메소드

이 메소드를 사용할 때는 데이터베이스에 쿼리를 날리게 된다. 

  • 반복
  • (인덱스 기반의) 슬라이싱
  • 피클링/캐싱
  • 쿼리셋을 출력할 수 있는 문자열로 반환: repr()
  • 쿼리셋의 객체 수(길이) 리턴: len()
  • 쿼리셋을 리스트 타입으로 변경: list()
  • 쿼리셋 안에 객체가 존재하는지 판단: bool()

 

get()filter()의 차이

🌟SQL 쿼리의 차이

장고 내부에서 생성된 쿼리셋을 계산할 때는 데이터베이스에 쿼리를 날려야 한다. 즉 쿼리 메소드는 SQL문으로 변환될 수 있다. filter() 메소드의 경우 다음과 같이 변환될 수 있다. 

 

- ORM

Restaurant.objects.filter(name="seoul")

- SQL

SELECT 'id', 'name', 'category', 'email', 'menu'
FROM 'restaurant' WHERE 'restaurant'.'name' = 'seoul'

 

그러나 get() 메소드의 경우는 filter() 메소드와 달리 별다른 SQL 메소드로 변환될 수 없다. 

대신 filter() 메소드에 부가적인 처리를 해서 나타낼 수 있다. 

 

-ORM

Restaurant.objects.get(name='seoul')

-Code

rest = Restaurant.objects.filter(name='seoul')

if len(rest) == 1:
	return rest[0]
else:
	raise Exception

즉 get() 메소드는 filter() 메소드에 부가적인 처리를 한 결과를 리턴한다. 그러므로 같은 코드를 실행해도 filter()의 속도가 get()보다 빠르다.

 

뿐만 아니라, 인덱싱(rest[0])의 경우 쿼리셋에 해당하는 인덱스가 없다면 오류를 발생시킨다. 따라서 filter()는 조건에 해당하는 데이터의 개수에 상관 없이 결과를 리턴하지만, get()은 조건에 해당하는 데이터가 1개가 아닌 이상 오류를 반환한다. 

 

그러므로 get()은 반드시 한 개의 데이터만 리턴하고, 하나가 아니면 오류를 발생시킬 때만 사용하는 것이 더 효율적이라고 볼 수 있겠다! 

 

🌟쿼리셋을 리턴한다 vs 리턴하지 않는다

쿼리셋을 리턴하지 않는 메소드의 경우, 쿼리셋이 없기 때문에 캐시도 갖고 있지 않다. 그렇기 때문에 이 메소드들은 한 번 호출될 때마다 데이터베이스에 쿼리를 날리게 된다. get()은 쿼리셋을 리턴하지 않는 메소드들 중 하나이다. 

반면 filter()는 쿼리셋을 리턴한다. 따라서 filter()를 사용한다고 바로 쿼리를 날리는 것이 아니다. 그래서 메소드를 여러 번 사용할 경우, get() 보다는 filter()가 더 빠르다. 

 

Project라는 모델이 있다고 가정할 때, 아래의 두 코드의 성능은 다르다. 

# get()
p1 = Project.objects.get(id=1)
print(p1)
p2 = Project.objects.get(id=2)
print(p2)
p3 = Project.objects.get(id=3)
print(p3)
p4 = Project.objects.get(id=4)
print(p4)
# filter()
proj = Project.objects.filter(id__lte=4)
for p in proj:
	print(p)

위의 코드는 get을 사용하여 총 4번 데이터베이스에 쿼리를 날리지만, 아래 코드는 filter()를 사용해 쿼리셋을 생성하고, for문을 사용해서 쿼리셋 안의 객체를 문자열로 출력할 때만 데이터베이스에 쿼리를 날린다. 

 

따라서 순서(ordering)가 별 상관이 없다면, get()을 여러 번 사용하는 것 보다는 filter()를 사용해서 적합한 쿼리셋을 생성한 뒤, for문 등을 이용해서 데이터베이스 접근을 최소화하는 것이 더 성능이 좋다. 

 

🌟여러 개의 데이터를 포함할 수 있다 vs 하나의 데이터만 포함한다

또한 filter()는 쿼리셋을 리턴하기에 여러 개의 데이터를 쿼리셋으로 받을 수 있지만, get()은 하나의 데이터만 받을 수 있다. 만약 get() 실행 시 해당하는 데이터가 없다면 Model.DoesNotExist 에러가 발생하고, 또는 해당하는 데이터가 여러 개라면 Model.MultipleObjectsReturned 에러가 발생한다. 또한 해당하는 데이터가 여러 개인 경우, 오류가 발생하기 전에 쿼리의 실행 속도가 상당히 느려지기도 한다. 

 

따라서 get()을 사용해야 한다면 반드시 unique한 컬럼명 또는 인덱스 컬럼명, primary key 등을 사용해서 해당하는 데이터가 여러 개 나오지 않도록 주의하자. 

 

 

참고한 포스트

Django에서 DB 액세스 최적화하기 – Myungseo Kang

QuerySet API reference | Django 문서 | Django (djangoproject.com)

Making queries | Django documentation | Django (djangoproject.com)

django - filter().exists(), Q() 객체, F() 객체 (velog.io)

 

'server-side > Django' 카테고리의 다른 글

signals  (0) 2023.06.21
Model.select_related() vs Model.prefetch_related()  (0) 2022.07.11
models: on_delete  (0) 2022.07.05
admin: Inline, InlineModelAdmin  (0) 2022.07.02
conda 사용해서 가상환경 만들기  (0) 2022.06.29

+ Recent posts