[Re:Django] 12. Query Expressions, F()

Magit·2020년 5월 6일
1

Django

목록 보기
12/13

쿼리 표현식

  • create, update, filter, order by, annotation, aggreagate의 일부로 사용할 수 있는 값 혹은 표현식을 일컫는다.
  • 쿼리 표현식은 프로젝트의 안전성과 성능을 대폭 향상시켜주기에 익혀두는게 좋다.
  • Query Expressions 공식문서
  • 나쁜 예제
    • 모든 고객 레코드에 대해서 for loop가 돈다. => 매우 느리고, 메모리 소모가 크다.
    • 경합상황 (race condition)에 직면할 가능성이 크다. => 데이터 분실 우려

경합상황이란?
다중 프로그래밍 시스템이나 다중 처리기 시스템에서 두 명령어가 동시에 같은 기억 장소를 액세스할 때 그들 사이의 경쟁에 의해 수행 결과를 예측할 수 없게 되는 것.

# 절대 따라하지 말 것
from models.customers import Customer

customers = []
for customer in Customer.objects.iterator():
  if customer.scoops_ordered > customer.store_visits:
    customers.append(customer)
  • 수정 후
    • 쿼리표현식을 통해 코드들이 서로 경합 펼치는 상황을 피한다.
from django.db.models import F
from models.customers import Customer

customers = Customer.objects.filter(scoops_ordered__gt=F('store_visits'))

위 코드 쿼리표현식 중 F() expressions의 기능

  • 파이썬이 아닌 데이터베이스 자체 내에서 해당 조건을 비교하는 기능을 갖는다.
  • 경합조건을 피할 수 있다. Avoiding race conditions using F()
  • 위 코드에서 내부적으로 다음과 같은 SQL문을 생성한다.
  SELECT * from customers_customer where scoops_ordered > store_visits

F(name) 에 대해서 더 자세히 알아보기

F() 객체는 모델 필드 혹은 어노테이트된 열의 값을 나타낸다. 데이터베이스에서 파이썬 메모리로 데이터를 갖고오지 않고 모델 필드 값을 참조해 사용하여 데이터베이스 작업을 수행할 수 있다. 장고는 F() 객체를 사용하여 데이터베이스 수준에서 필요한 작업을 설명하는 SQL 표현식을 생성하게된다.

reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()

위 파이썬 구문은 reporter.stories_filed의 값을 데이터베이스에서 메모리로 갖고와 파이썬 연산자를 이용해서 조작하고 데이터베이스에 저장한것이다.

하지만 이 작업을 다음과 같이 할 수도 있다.

from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()

reporter.stories_filed = F('stories_filed') + 1 는 값을 인스턴스 속성에 할당 한 것 처럼 보이지만 데이터베이스에 대한 연산을 설명하는 SQL 구문이다. Django는 F()의 인스턴스를 만나면 파이썬 연산자를 오버라이딩하여 캡슐화된 SQL 표현식을 생성한다.
이 경우 reporter.stories_filed가 나타내는 데이터베이스 필드를 증가 시키도록 데이터베이스에 지시한다. reporter.stories_filed 나 다른 어떠한 값이 있어도 데이터베이스에 의해 처리되기 때문에 파이썬은 알지 못한다.

위 값으로 저장한 새 값을 얻으려면 다시 호출해야한다.

reporter = Reporters.objects.get(pk=reporter.pk)

F()를 사용하면 race condition을 피할 수 있다.

F() 의 또다른 유용한 이점은 데이터베이스가 필드 값을 업데이트하면 race condition을 피할 수 있다는 점이다.
첫번째 예시(F() 미사용)는 두개의 파이썬 thread를 사용해서 실행하고 A라는 thread가 값을 받아 파이썬 메모리에 저장할 때, B 라는 thread는 값을 추출, 증가, 저장할 수 있다. A 라는 thread는 값을 증가, 저장하게 되면 B라는 thread는 작업했던 내용이 손실된다. A는 B의 작업 이전에 값을 갖고왔기 때문이다.

F() 를 사용하면 위 문제를 해결할 수 있다. 메모리에서 갖고온 값을 기반으로 작업하는 게 아니라 save() 나 update()가 실행될 때, 데이터베이스의 필드 값을 기준으로 작업하기 때문이다.

F()의 여러가지 사용법

filter() 에서 사용하기

SELECT a, b, c, d
FROM test
WHERE a=c

위와 같은 쿼리가 있따면 F() 를 사용해서 아래와 같이 표현 가능하다.

from django.db.models import F

Test.objects.filter(a=F('c'))

annotate()에서 사용 - 필드명 변경하기

SELECT a,b,c,d as dddd
FROM test

위와 같이 d라는 컬럼의 이름을 dddd로 바꿀 때, F()를 사용해서 아래와 같이 표현 가능하다.

from django.db.models import F

Test.objects.annotate(dddd=F('d'))

order_by() 에서 사용하기

from django.db.models import F
Company.object.order_by(F('last_contacted').desc(nulls_last=True))

Company 모델 인스턴스들을 last_contacted 필드 기준으로 내림차순 정렬하되, 해당 필드가 null이면 제일 뒤로 보낸다는 의미의 코드이다.
nulls_first 옵션이다 .asc() 메서드도 있다.

profile
이제 막 배우기 시작한 개발자입니다.

0개의 댓글