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

Django Doc에서

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

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

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

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



답변

당신의 이해는 대부분 정확합니다. 당신은 사용 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중복 된 개체가 각 “부모”개체에 대한 파이썬으로 작성됩니다. 파이썬의 객체는 상당한 메모리 오버 헤드를 가지고 있기 때문에 고려할 수도 있습니다.


답변

두 가지 방법 모두 동일한 목적을 달성하여 불필요한 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);

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


답변

장고 문서에 따르면

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 관련


답변

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

관련있는 장고 모델이 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개체를 한 번만 쿼리하여 성능을 향상시킬 수 있습니다.


답변