Search
🔎

Django Query Expressions 정리

목차

Query Expressions란?

update(), create(), filter(), order_by(), annotation(), aggregate() 메소드들의 일부로 사용할 수 있는 값이나 표현식을 말한다.
만약 for loop 등을 사용하여 모든 레코드를 사용한다면, 속도와 메모리 사용 측면 등 매우 불리하다.
또한, 경쟁 조건 (race condition)이 발생할 수 있다.

경쟁 조건 (race condition)이란?

다중 프로그래밍(처리) 시스템에서 여러 명령어가 동시에 같은 기억 장소를 액세스할 때 그들 사이의 경쟁에 의해 수행 결과를 예측할 수 없게 되는 것이다. 이로 인해 데이터 손실이나 무결성에 문제가 발생할 수 있다.
나쁜 예시
모든 레코드를 조회하고, 조건을 비교하기 때문에 속도와 메모리 사용 측면에서 매우 불리하다.
from .models import User all_users = User.objects.all() users = [] # for loop로 모든 필드를 조회한다. for user in all_users.iterator(): if user.field1 < user.field2: users.append(user)
Python
복사
좋은 예시
F() expressions를 사용하여 Python이 아닌 DB에서 아래와 같은 SQL을 생성하여 비교하도록 한다.
F()는 아래에서 설명하도록 하겠다.
from django.db.models import F from .models import User all_users = User.objects.all() # F()를 사용하여, 표현식을 생성한 후 필터링한다. users = User.objects.filter(field1__gt=F('field2'))
Python
복사
# User.objects.filter(field1__gt=F('field2')) SELECT * from user where field1 > field2
SQL
복사

F()란?

F() 객체는 모델의 특정 필드나 주석된 열의 값을 나타낸다...? 라고 하는데 글만 봐선 바로 머리에 들어오진 않는다.
여러 블로그의 내용을 참조하면 매번 DB에서 Python 메모리로 데이터를 가져오지 않고, 그 연산에 맞는 SQL문을 만들어 작업할 수 있는 것이다.
이해를 위해 공식문서에 있는 예시를 사용하여 설명하겠다.

F()를 사용하지 않은 예시

이 예시는 F() 객체를 사용하지 않은 일반적인 값 수정이다.
reporter = Reporters.objects.get(name='Tintin') reporter.stories_filed += 1 reporter.save()
Python
복사
위 예시의 순서는 아래와 같다.
여기서 핵심은 2 ~ 4번이다. DB → Python 메모리 → DB로 이동하며 사용한다. 저장되는 값도 Python에서 처리한 값인 2를 저장한다.
1.
get() 메서드로 객체를 얻어온다.
2.
객체의 stories_filed 값을 Python 메모리에 가져온다.
3.
Python의 연산자를 이용해 값을 1 증가시킨다.
4.
save() 메서드를 사용하여 DB에 저장한다. 즉, 1에서 1이 증가된 2라는 값이 저장된다.
위 예시에서는 get() 메서드를 사용한 하나의 객체만 조작하였다.
하지만 filter() 메서드 등을 활용한 많은 양의 객체를 얻어온 후, 모든 객체에 대한 값 변경이 필요하다면 어떨까?
아래 처럼 각 객체마다 작업을 반복해야한다.
DB에서 값 얻기 → Python 메모리에 저장 후 변경 → DB에 다시 저장
DB에서 값 얻기 → Python 메모리에 저장 후 변경 → DB에 다시 저장
...
매번 이럴수도 없는 노릇... 심지어 코드가 동시다발적으로 동작하는 경우 값의 문제가 발생할 수 있다.
이제 F()가 나설 차례다.

F()를 사용한 예시

이 예시는 F() 객체를 사용하지 않은 일반적인 값 수정이다.
from django.db.models import F reporter = Reporters.objects.get(name='Tintin') reporter.stories_filed = F('stories_filed') + 1 reporter.save()
Python
복사
위 예시의 순서는 아래와 같다.
1.
get() 메서드로 객체를 얻어온다.
2.
객체의 stories_filed 값을 F('stories_filed') + 1 를 저장한다.
3.
변경된 stories_filed 값을 DB에 반영한다.
2번 과정에서 +1 을 하였으니, 메모리에 얻어온 값을 사용해 Python 연산자를 이용하여 값을 변경하게 아닌가? 라는 생각이 들 수 있다.
하지만, Django에서는 F()를 만나면 Python 연산자를 오버라이딩하여 연산에 맞는 SQL 구문을 생성한다고 한다.
즉, 2라는 값을 직접 저장하는게 아니고 현재 값에서 + 1된 값을 저장하는 SQL 구문인 것이다.
save() 메서드 사용 시 해당 구문이 실행된다.
이렇게 F()를 사용한 방법을 사용하면 DB에서 값을 매번 가져오지 않고, DB에서 바로 작업하기 때문에 race condition을 피할 수 있으며 속도와 메모리 사용 면에서도 매우 효율적이다.

F() 사용 시 주의 점

이전에 설명했듯 F() 객체를 사용하여 값을 변경하였을 때, 값이 직접 대입되는게 아닌 연산에 맞는 SQL 구문을 적용시키는 것이다.
F() 객체는 모델의 인스턴스를 저장한 후에도 값이 유지되기 때문에, 추가로 save()를 진행하면 SQL구문이 또 적용된다.
... reporter.stories_filed = F('stories_filed') + 1 reporter.save() repoter.ex_field = 'test' repoter.save() # stories_filed값이 또 1증가한다.
Python
복사
이때는 refresh_from_db() 를 사용하여 DB에서 값을 로드하게 되면 이러한 지속성을 막을 수 있다.
from django.db.models import F reporter = Reporters.objects.get(name='Tintin') reporter.stories_filed = F('stories_filed') + 1 reporter.save() repoter.refresh_from_db()
Python
복사

참고

이름
태그
URL
COUNT5