Django ORM에서 select_related와 prefetch_related의 차이점은 무엇입니까?


291

Django Doc에서

select_related() 외래 키 관계를 "추종"하여 쿼리를 실행할 때 추가 관련 개체 데이터를 선택합니다.

prefetch_related() 각 관계에 대해 별도의 조회를 수행하고 Python에서 "결합"합니다.

"파이썬에 참여하는 것"이란 무엇을 의미합니까? 누군가가 예를 들어 설명 할 수 있습니까?

내 이해는 외래 키 관계에 사용 select_related; M2M 관계에는을 사용하십시오 prefetch_related. 이 올바른지?


2
파이썬에서 조인을 수행한다는 것은 데이터베이스에서 조인이 발생하지 않음을 의미합니다. select_related를 사용하면 데이터베이스에서 조인이 발생하고 데이터베이스 쿼리는 하나만 발생합니다. prefetch_related를 사용하면 두 개의 쿼리를 실행 한 다음 ORM에 의해 결과가 '결합'되므로 여전히 object를 입력 할 수 있습니다. related_set
Mark Galloway

3
각주로서, Timmy O'Mahony는 데이터베이스 적중을 사용하여 차이점을 설명 할 수도 있습니다. link
Mærcos

답변:


423

당신의 이해는 대부분 정확합니다. 당신은 사용 select_related이 선택 될 거라고 객체가 단일 객체, 그래서 때 OneToOneFieldForeignKey. 당신 은 당신이 언급 한 것과 반대로 prefetch_related"집합"을 얻을 때 사용 합니다 . "reverse s"의 의미를 명확히하기 위해 여기에 예가 있습니다.ManyToManyFieldForeignKeyForeignKey

class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

차이점은 select_relatedSQL 조인을 수행하므로 SQL 서버에서 테이블의 일부로 결과를 다시 가져옵니다. prefetch_related반면에 다른 쿼리를 실행하므로 원래 개체의 중복 열이 줄어 듭니다 ( ModelA위 예에서). 사용할 prefetch_related수있는 모든 것에 사용할 수 있습니다 select_related.

트레이드 오프는 prefetch_related서버로 다시 선택하기 위해 ID 목록을 작성하여 보내야 한다는 점입니다. 시간이 오래 걸릴 수 있습니다. 거래 에서이 작업을 수행하는 좋은 방법이 있는지 확실하지 않지만 Django는 항상 목록을 보내고 SELECT ... WHERE pk IN (..., ..., ...)라고 말합니다. 원래. 이 경우 프리 페치 된 데이터가 드문 경우 (사람의 주소에 연결된 미국 주 개체라고하자)이 방법은 매우 좋지만 일대일에 가까울 경우 많은 통신이 낭비 될 수 있습니다. 확실하지 않은 경우 두 가지를 모두 시도하고 어느 것이 더 나은지보십시오.

위에서 설명한 모든 것은 기본적으로 데이터베이스와의 통신에 관한 것입니다. 그러나 파이썬 측에서는 prefetch_related단일 객체를 사용하여 데이터베이스의 각 객체를 나타내는 추가 이점이 있습니다. 로 select_related중복 된 개체가 각 "부모"개체에 대한 파이썬으로 작성됩니다. 파이썬의 객체는 상당한 메모리 오버 헤드를 가지고 있기 때문에 고려할 수도 있습니다.


3
그래도 더 빠를까요?
elad silver

24
select_related하나의 쿼리이지만 prefetch_related2는 쿼리 이므로 전자가 더 빠릅니다. 그러나 select_related당신을 도움이되지 않습니다 ManyToManyField
bhinesley

31
@eladsilver 느리게 회신해서 죄송합니다. 실제로 다릅니다. select_relatedSQL에서 JOIN을 사용하는 반면 prefetch_related첫 번째 모델에서 쿼리를 실행하고 프리 페치하는 데 필요한 모든 ID를 수집 한 다음 WHERE의 IN 절을 사용하여 필요한 모든 ID를 사용하여 쿼리를 실행합니다. 동일한 외래 키를 사용하여 3-5 모델을 말하면 select_related거의 확실하게 좋습니다. 동일한 외래 키를 사용하는 모델이 100 대 또는 1000 대인 경우 prefetch_related실제로 더 좋습니다. 그 사이에 어떤 일이 발생하는지 테스트하고 확인해야합니다.
CrazyCasta

1
프리 페치 관련 "일반적으로 이해가되지 않습니다"에 대한 귀하의 의견에 이의를 제기 할 것입니다. 고유 한 것으로 표시된 FK 필드의 경우에도 마찬가지이지만 여러 행이 동일한 FK 값 (작성자, 사용자, 범주, 도시 등)을 갖는 모든 위치에서 프리 페치는 Django와 DB 사이의 대역폭을 줄이지 만 행은 복제하지 않습니다. 또한 일반적으로 DB에서 더 적은 메모리를 사용합니다. 이 중 하나는 종종 단일 추가 쿼리의 오버 헤드보다 중요합니다. 이것이 합리적으로 인기있는 질문에 대한 최고의 대답이라고 생각하면 대답에 언급해야한다고 생각합니다.
Gordon Wrigley

1
@GordonWrigley 그래, 글을 쓴 지 얼마되지 않아서 되돌아 가서 조금 명확 해졌다. "DB의 메모리를 적게 사용"하는 비트에 동의하지는 않지만 모든 것에 동의합니다. 그리고 파이썬 측에서 더 적은 메모리를 사용할 수 있습니다.
CrazyCasta

26

두 가지 방법 모두 동일한 목적을 달성하여 불필요한 DB 쿼리를 방지합니다. 그러나 그들은 효율성을 위해 다른 접근법을 사용합니다.

이러한 방법 중 하나를 사용하는 유일한 이유는 하나의 큰 쿼리가 많은 작은 쿼리보다 선호되는 경우입니다. Django는 큰 쿼리를 사용하여 데이터베이스에 대해 주문형 쿼리를 수행하지 않고 메모리에 모델을 선제 적으로 만듭니다.

select_related각 조회마다 조인을 수행하지만 모든 조인 된 테이블의 열을 포함하도록 선택을 확장합니다. 그러나이 방법에는주의가 필요합니다.

조인은 쿼리의 행 수를 곱할 수 있습니다. 외래 키 또는 일대일 필드에 대해 조인을 수행하면 행 수가 증가하지 않습니다. 그러나 다 대다 조인에는이 보장이 없습니다. 따라서 Django select_related는 예기치 않게 큰 조인을 초래하지 않는 관계로 제한 됩니다.

"파이썬에 참여" 에 대한이 prefetch_related더 다음이 있어야 할 놀라운 조금. 결합 할 각 테이블에 대해 별도의 쿼리를 작성합니다. 다음과 같이 WHERE IN 절을 사용하여 이러한 각 테이블을 필터링합니다.

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

잠재적으로 너무 많은 행으로 단일 조인을 수행하는 대신 각 테이블이 별도의 쿼리로 분할됩니다.


1

장고 문서에 따르면

prefetch_related ()

지정된 각 조회에 대해 단일 배치에서 관련 개체를 자동으로 검색하는 QuerySet을 반환합니다.

이것은 select_related와 비슷한 목적을 가지고 있습니다. 둘 다 관련 객체에 액세스하여 발생하는 데이터베이스 쿼리의 유출을 막기 위해 설계되었지만 전략은 상당히 다릅니다.

select_related는 SQL 조인을 만들고 SELECT 문에 관련 개체의 필드를 포함시켜 작동합니다. 이러한 이유로 select_related는 동일한 데이터베이스 쿼리에서 관련 개체를 가져옵니다. 그러나 '많은'관계를 통해 결합하여 발생하는 훨씬 더 큰 결과 집합을 피하기 위해 select_related는 외래 ​​키 및 일대일 관계로 단일 값 관계로 제한됩니다.

반면에 prefetch_related는 각 관계에 대해 별도의 조회를 수행하고 Python에서 '결합'을 수행합니다. 이를 통해 select_related가 지원하는 외래 키 및 일대일 관계 외에 select_related를 사용하여 수행 할 수없는 다 대다 및 다 대일 객체를 프리 페치 할 수 있습니다. 또한 GenericRelation 및 GenericForeignKey 프리 페치를 지원하지만 동종 결과 집합으로 제한되어야합니다. 예를 들어, GenericForeignKey가 참조하는 오브젝트 프리 페치는 조회가 하나의 ContentType으로 제한되는 경우에만 지원됩니다.

이에 대한 자세한 정보 : https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch 관련


1

이미 게시 된 답변을 살펴 보았습니다. 실제 예제로 답변을 추가하면 더 좋을 것이라고 생각했습니다.

관련있는 장고 모델이 3 개 있다고 가정 해 봅시다.

class M1(models.Model):
    name = models.CharField(max_length=10)

class M2(models.Model):
    name = models.CharField(max_length=10)
    select_relation = models.ForeignKey(M1, on_delete=models.CASCADE)
    prefetch_relation = models.ManyToManyField(to='M3')

class M3(models.Model):
    name = models.CharField(max_length=10)

여기에서 조회 할 수 있습니다 M2모델과 상대 M1사용하여 객체 select_relation필드 및 M3사용하여 객체 prefetch_relation필드.

우리가 언급 한 그러나 같은 M1에서의 관계 M2입니다 ForeignKey, 그냥만을 반환 모든 녹음 M2개체를. 같은 것도 적용됩니다 OneToOneField.

그러나 M3의 관계 M2ManyToManyField많은 수의 M1객체를 반환 할 수 있습니다.

당신이이 경우 고려 M2객체 m21, m22같은이 5 관련 M3ID를 가진 개체를 1,2,3,4,5. M3각 객체에 대해 연결된 객체를 가져올 때 M2select related을 사용하면 이것이 작동하는 방식입니다.

단계 :

  1. m21물체를 찾으십시오 .
  2. ID가 인 M3객체와 관련된 모든 객체를 쿼리합니다 .m211,2,3,4,5
  3. m22객체와 다른 모든 객체에 대해 동일한 것을 반복하십시오 M2.

우리가 같은 것처럼 1,2,3,4,5모두에 대해 ID를 m21, m22우리는 옵션을 select_related 사용하는 경우, 이미 인출 된 것과 동일한 ID에 대해 두 번 DB를 쿼리 것, 객체.

대신 prefetch_related를 사용하면 M2객체 를 가져올 때 M2테이블 을 쿼리하는 동안 객체가 반환 한 모든 ID (참고 : ID 만)를 기록하고 마지막 단계에서 Django는 M3테이블에 쿼리 합니다. M2객체가 반환 한 모든 ID 세트와 함께 . 과에 가입 M2데이터베이스 대신 파이썬을 사용하여 객체.

이렇게하면 모든 M3개체를 한 번만 쿼리하여 성능을 향상시킬 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.