본문으로 바로가기

Django QuerySet에 대하여

category Coding/Django 2019. 4. 29. 12:02
반응형

QuerySet is Lazy

Django의 QuerySet은 Lazy하다. 한마디로 코드 상에서 쿼리를 구성해도 실제 데이터베이스에 접근하는 것은 평가(Evaluate)될 때 한번 뿐이다. 따라서 이러한 특성을 잘 이용한다면 데이터베이스에 접근하는 횟수를 줄일 수 있고 쿼리를 최적화하여 퍼포먼스를 증가시킬 수 있다.


When QuerySets are evaluated

내부적으로 QuerySet은 실제로 데이터베이스에 접근하지않고 필터링, 슬라이싱등의 작업을 할 수 있다. 한마디로 평가(Evaluate)되기 전까지 데이터베이스에 접근하지 않는다는 말이다. 아래와 같은 동작을 했을 때 QuerySet이 평가되고 실제 데이터베이스에 접근한다.


- Iteration : QuerySet은 Iterable(순회 가능하다는 말)하다. 그리고 최초 순회할때 데이터베이스에 접근한다. 예를 들어 아래의 예제는 데이터베이스에 들어있는 모든 Entry모델의 headline값을 출력한다.


for e in Entry.objects.all():
    print(e.headline)


- Slicing : QuerySet은 Python List의 슬라이싱 문법을 사용하여 슬라이싱할 수 있다. 평가되지 않은 QuerySet을 슬라이싱하면 평가되지 않은 QuerySet의 값이 리턴되지만 만약 "step" 파라미터를 같이 사용한다면 장고에서는 실제 데이터베이스에 쿼리를 날린다. 한마디로 슬라이싱은 평가된다는 말이다.


- Pickling/Cachinghttps://docs.djangoproject.com/en/2.1/ref/models/querysets/#pickling-querysets 참고


- repr() : repr()을 호출하면 QuerySet은 평가된다.


- len() : len()을 호출하면 QuerySet은 평가된다.


- list() : list()을 호출하면 QuerySet은 평가된다.


- bool() : bool(), or, and, if문을 사용하면 QuerySet은 평가된다. 


Caching and QuerySets

각각의 QuerySet은 데이터베이스 액세스를 최소화하기 위해 캐시를 포함하고 있다. QuerySet이 어떠한 방식으로 작동하는지 이해하면 보다 효율적인 코드를 작성할 수 있을 것이다.


처음 QuerySet이 생성되면 캐시는 빈 상태로 생성된다. 처음 QuerySet이 평가되면, Django는 쿼리의 결과를 캐시에 저장시킨다. 그 다음의 평가는 데이터베이스에 접근하지않고 캐시에 저장된 결과에 접근하여 처리한다. (여기에서 평가(Evaluate)란, 쉽게 말해서 데이터베이스에 접근하는 것이라고 생각해도 무방할 것 같다)


예를 들어 아래의 예제는 2개의 QuerySet을 만들고 평가한다.


>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])


위 예제는 같은 데이터베이스 쿼리를 2번 실행시킨다. 또한 2개의 결과는 같지 않을 수 있다. 첫번째가 실행된 이후 두번째가 실행되는 사이에 레코드가 삭제되거나 업데이트 될 수 있기 때문이다. 따라서 이 문제를 피하려면 아래와 같이 QuerySet을 저장시키고 재사용하면 된다.


>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.


When QuerySets are not Cached

QuerySet이 항상 결과를 캐싱하는것은 아니다. 만약 QuerySet의 일정 부분만을 평가하는 경우 캐시에 저장되지 않는다. 예를 들어 아래처럼 동일한 index의 QuerySet 결과에 접근하면 각각의 명령어가 실행될 때마다 데이터베이스에 접근한다.


>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again


하지만 전체 QuerySet을 저장해놓고 재사용한다면, QuerySet은 캐싱되고 그 이후의 명령어는 캐시를 참조한다. (데이터베이스에 접근하지 않는다)


>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache


전체 QuerySet을 평가하고 캐싱한다음 다음 명령어부터 캐시를 참조하는 또 다른 예제이다.


>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)



반응형