Django에서 icontains의 번역
요약
Django
에서 PostgreSQL
을 사용할 때 <something>.filter(<field>__icontains=<target>)
는 ilike
가 아니라 UPPER(field) like UPPER(target)
으로 변환된다. field
에 인덱스를 걸어놓은 경우 인덱스를 타지 못하고 ilike
를 이용해야 인덱스를 탄다. 이런 부분은 날 것 그대로의 SQL을 이용해야 한다.
기대
from django.db import models
from django.contrib.postgres.indexes import GinIndex
class Post(models.Model):
title = models.CharField(blank=False, null=False, max_length=50)
content = models.TextField(blank=True, null=False)
class Meta:
indexes = [
GinIndex(
name="title_idx", fields=["title"], opclasses=["gin_trgm_ops"]
),
]
from django.db import models
from django.contrib.postgres.indexes import GinIndex
class Post(models.Model):
title = models.CharField(blank=False, null=False, max_length=50)
content = models.TextField(blank=True, null=False)
class Meta:
indexes = [
GinIndex(
name="title_idx", fields=["title"], opclasses=["gin_trgm_ops"]
),
]
위와 같이 Post
모델의 title
에 대해 인덱스를 걸어놓고 다음과 같은 SQL을 날려보자.
SELECT "board_post"."id",
"board_post"."issued_date",
"board_post"."last_modified",
"board_post"."title",
"board_post"."content"
FROM "board_post"
WHERE ("board_post"."issued_date" >= '2020-12-28T19:51:54.775082+09:00'::timestamptz AND "board_post"."issued_date" <= '2021-06-28T19:51:54.775025+09:00'::timestamptz AND "board_post"."title"::text ILIKE '%테스트%')
ORDER BY "board_post"."issued_date" DESC
LIMIT 21;
39ms이 걸렸고, 모두 인덱스를 잘 활용하는 것을 볼 수 있다.
장고는 UPPER
를 이용
앞선 쿼리와 동일하게 동작하지만 생긴 것만 살짝 다른 쿼리를 날려보자.
SELECT "board_post"."id",
"board_post"."issued_date",
"board_post"."last_modified",
"board_post"."title",
"board_post"."content"
FROM "board_post"
WHERE ("board_post"."issued_date" >= '2020-12-28T19:51:54.775082+09:00'::timestamptz AND "board_post"."issued_date" <= '2021-06-28T19:51:54.775025+09:00'::timestamptz AND UPPER("board_post"."title"::text) ILIKE UPPER('%테스트%'))
ORDER BY "board_post"."issued_date" DESC
LIMIT 21;
위 쿼리는 얼마나 걸릴까? 285ms가 걸렸다. 어째서?
테이블에서 인덱스를 전혀 활용하지 못하고 Full scan을 하고 있기 때문이다. 그리고 장고는 위와 같이 icontains
를 번역한다.
q = Post.objects.filter(title__icontains="테스트")
print(q.query)
어떤 결과가 나올까?
UPPER("board_post"."title"::text) LIKE UPPER(%테스트%))
위와 같이 양 변에 upper를 적용한 sql이 나오게 된다. 따라서 인덱스를 타지 못한다.
고치기
위 상황은 extra
를 이용해서 조건문에 쿼리문을 직접 삽입함으로써 고칠 수 있다. 다음과 같이 쓰자.
<queryset>.extra(
where=[""""board_post"."title"::text ILIKE %s"""], params=[f"%테스트%"]
)
위와 같이 강제로 ilike
를 사용하게 함으로써 고칠 수 있다. 이제는 처음 기대했던 것과 같은 쿼리가 나와 인덱스를 탈 수 있다.
Author And Source
이 문제에 관하여(Django에서 icontains의 번역), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@heka1024/Django에서-icontains의-번역저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)