Sự khác biệt giữa select_related và prefetch_related trong Django ORM là gì?


291

Trong tài liệu Django,

select_related() "Theo dõi" các mối quan hệ khóa ngoài, chọn dữ liệu đối tượng liên quan bổ sung khi thực hiện truy vấn của nó.

prefetch_related() thực hiện tra cứu riêng cho từng mối quan hệ và thực hiện "tham gia" trong Python.

"Việc tham gia trăn" có nghĩa là gì? Ai đó có thể minh họa bằng một ví dụ?

Hiểu biết của tôi là đối với mối quan hệ nước ngoài, sử dụng select_related; và đối với mối quan hệ M2M, sử dụng prefetch_related. Điều này có đúng không?


2
Thực hiện phép nối trong python có nghĩa là phép nối sẽ không xảy ra trong cơ sở dữ liệu. Với select_related, phép nối của bạn xảy ra trong cơ sở dữ liệu và bạn chỉ phải chịu một truy vấn cơ sở dữ liệu. Với prefetch_related, bạn sẽ thực hiện hai truy vấn và sau đó kết quả sẽ được 'tham gia' bởi ORM để bạn vẫn có thể nhập object.related_set
Mark Galloway

3
Như một chú thích, Timmy O'Mahony cũng có thể giải thích sự khác biệt của họ bằng cách sử dụng các lần truy cập cơ sở dữ liệu: link
Mærcos

Điều này có thể giúp bạn họcbatta.com / blog / work
with

Câu trả lời:


424

Sự hiểu biết của bạn chủ yếu là chính xác. Bạn sử dụng select_relatedkhi đối tượng bạn sẽ chọn là một đối tượng, OneToOneFieldhoặc a ForeignKey. Bạn sử dụng prefetch_relatedkhi bạn sẽ nhận được một "bộ" các thứ, vì vậy ManyToManyFieldnhư bạn đã nêu hoặc đảo ngược ForeignKeys. Chỉ cần làm rõ những gì tôi có nghĩa là "đảo ngược ForeignKey" đây là một ví dụ:

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

Sự khác biệt là select_relatedtham gia SQL và do đó lấy lại kết quả như một phần của bảng từ máy chủ SQL. prefetch_relatedmặt khác thực hiện một truy vấn khác và do đó làm giảm các cột dư thừa trong đối tượng ban đầu ( ModelAtrong ví dụ trên). Bạn có thể sử dụng prefetch_relatedcho bất cứ điều gì mà bạn có thể sử dụng select_relatedcho.

Sự đánh đổi là prefetch_relatedphải tạo và gửi một danh sách ID để chọn trở lại máy chủ, việc này có thể mất một lúc. Tôi không chắc có cách nào tốt để thực hiện việc này trong giao dịch hay không, nhưng tôi hiểu là Django luôn chỉ gửi một danh sách và nói CHỌN ... WHERE pk IN (..., ..., ...) về cơ bản. Trong trường hợp này nếu dữ liệu được tìm nạp trước thưa thớt (giả sử các đối tượng của Tiểu bang Hoa Kỳ được liên kết với địa chỉ của mọi người) thì điều này có thể rất tốt, tuy nhiên nếu nó gần với một hơn một, điều này có thể lãng phí rất nhiều thông tin liên lạc. Nếu nghi ngờ, hãy thử cả hai và xem cái nào hoạt động tốt hơn.

Tất cả mọi thứ thảo luận ở trên về cơ bản là về các giao tiếp với cơ sở dữ liệu. Tuy nhiên, về phía Python prefetch_relatedcó thêm lợi ích là một đối tượng được sử dụng để đại diện cho từng đối tượng trong cơ sở dữ liệu. Với select_relatedcác đối tượng trùng lặp sẽ được tạo bằng Python cho từng đối tượng "cha mẹ". Vì các đối tượng trong Python có một chút chi phí bộ nhớ, điều này cũng có thể được xem xét.


3
cái gì nhanh hơn mặc dù?
bạc elad

24
select_relatedlà một truy vấn trong khi prefetch_relatedlà hai, vì vậy truy vấn trước nhanh hơn. Nhưng select_relatedsẽ không giúp bạn cho ManyToManyField's
bhinesley

31
@eladsilver Xin lỗi vì trả lời chậm. Nó thực sự phụ thuộc. select_relatedsử dụng THAM GIA trong SQL trong khi prefetch_relatedchạy truy vấn trên mô hình đầu tiên, thu thập tất cả các ID cần thiết để tìm nạp trước và sau đó chạy truy vấn có mệnh đề IN trong WHERE với tất cả các ID mà nó cần. Nếu bạn đã nói 3-5 mô hình sử dụng cùng một khóa ngoại, select_relatedgần như chắc chắn sẽ tốt hơn. Nếu bạn có 100 hoặc 1000 mô hình sử dụng cùng một khóa ngoại, prefetch_relatedthực sự có thể tốt hơn. Ở giữa bạn sẽ phải kiểm tra và xem điều gì sẽ xảy ra.
CrazyCasta

1
Tôi sẽ tranh luận nhận xét của bạn về liên quan đến tìm nạp trước "nói chung không có ý nghĩa nhiều". Điều đó đúng với các trường FK được đánh dấu là duy nhất, nhưng bất cứ nơi nào có nhiều hàng có cùng giá trị FK (tác giả, người dùng, danh mục, thành phố, v.v.) đều tìm cách cắt giảm băng thông giữa Django và DB nhưng không trùng lặp các hàng. Nó cũng thường sử dụng ít bộ nhớ hơn trên DB. Một trong hai điều này thường quan trọng hơn chi phí của một truy vấn bổ sung. Cho rằng đây là câu trả lời hàng đầu cho một câu hỏi khá phổ biến tôi nghĩ rằng cần phải lưu ý trong câu trả lời.
Gordon Wrigley

1
@GordonWrigley Vâng, đã được một thời gian kể từ khi tôi viết nó, vì vậy tôi đã quay lại và làm rõ một chút. Tôi không chắc chắn tôi đồng ý với bit "sử dụng ít bộ nhớ trên DB", nhưng có với mọi thứ. Và nó có thể chắc chắn sử dụng ít bộ nhớ hơn về phía Python.
CrazyCasta

26

Cả hai phương thức đều đạt được cùng một mục đích, để từ bỏ các truy vấn db không cần thiết. Nhưng họ sử dụng các phương pháp khác nhau cho hiệu quả.

Lý do duy nhất để sử dụng một trong hai phương pháp này là khi một truy vấn lớn duy nhất thích hợp với nhiều truy vấn nhỏ. Django sử dụng truy vấn lớn để tạo ra các mô hình trong bộ nhớ thay vì thực hiện các truy vấn theo yêu cầu đối với cơ sở dữ liệu.

select_relatedthực hiện nối với mỗi lần tra cứu, nhưng mở rộng vùng chọn để bao gồm các cột của tất cả các bảng đã nối. Tuy nhiên phương pháp này có một cảnh báo.

Tham gia có khả năng nhân số lượng hàng trong một truy vấn. Khi bạn thực hiện nối với khóa ngoại hoặc trường một đối một, số lượng hàng sẽ không tăng. Tuy nhiên, nhiều người tham gia không có sự đảm bảo này. Vì vậy, Django hạn chế các select_relatedmối quan hệ không mong đợi dẫn đến sự tham gia lớn.

Các "tham gia trong python" cho prefetch_relatedlà nhiều hơn một chút đáng báo động sau đó nó nên được. Nó tạo ra một truy vấn riêng cho mỗi bảng được nối. Nó lọc từng bảng trong số này bằng mệnh đề WHERE IN, như:

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

Thay vì thực hiện một phép nối đơn lẻ có khả năng có quá nhiều hàng, mỗi bảng được chia thành một truy vấn riêng.


1

Như tài liệu của Django nói:

prefetch_related ()

Trả về một Bộ truy vấn sẽ tự động truy xuất, trong một lô, các đối tượng liên quan cho mỗi lần tra cứu được chỉ định.

Điều này có một mục đích tương tự với select_related, trong đó cả hai đều được thiết kế để ngăn chặn các truy vấn cơ sở dữ liệu gây ra bằng cách truy cập các đối tượng liên quan, nhưng chiến lược hoàn toàn khác nhau.

select_related hoạt động bằng cách tạo một phép nối SQL và bao gồm các trường của đối tượng liên quan trong câu lệnh SELECT. Vì lý do này, select_related lấy các đối tượng liên quan trong cùng một truy vấn cơ sở dữ liệu. Tuy nhiên, để tránh tập kết quả lớn hơn nhiều sẽ dẫn đến việc tham gia vào mối quan hệ 'nhiều người', select_related bị giới hạn ở các mối quan hệ có giá trị duy nhất - khóa ngoại và một đối một.

mặt khác, prefetch_related thực hiện tra cứu riêng cho từng mối quan hệ và thực hiện 'tham gia' trong Python. Điều này cho phép nó tìm nạp trước các đối tượng nhiều-nhiều và nhiều-một, không thể được thực hiện bằng cách sử dụng select_related, ngoài khóa ngoại và các mối quan hệ một-một được hỗ trợ bởi select_related. Nó cũng hỗ trợ tìm nạp trước GenericRelation và GenericForeignKey, tuy nhiên, nó phải được giới hạn trong một tập hợp kết quả đồng nhất. Ví dụ: các đối tượng tìm nạp trước được tham chiếu bởi GenericForeignKey chỉ được hỗ trợ nếu truy vấn bị giới hạn ở một ContentType.

Thông tin thêm về điều này: https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related


1

Đi qua các câu trả lời đã được đăng. Chỉ cần nghĩ rằng sẽ tốt hơn nếu tôi thêm một câu trả lời với ví dụ thực tế.

Giả sử bạn có 3 mẫu Django có liên quan.

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)

Tại đây bạn có thể truy vấn M2mô hình và các đối M1tượng tương đối của nó bằng cách sử dụng select_relationtrường và M3các đối tượng sử dụng prefetch_relationtrường.

Tuy nhiên, như chúng tôi đã đề cập đến M1mối quan hệ từ M2a ForeignKey, nó chỉ trả về 1 bản ghi cho bất kỳ M2đối tượng nào . Điều tương tự cũng áp dụng cho OneToOneField.

Nhưng M3mối quan hệ từ đó M2ManyToManyFieldcó thể trả về bất kỳ số lượng M1đối tượng nào.

Hãy xem xét một trường hợp bạn có 2 M2đối tượng m21, m22người có cùng 5 liên quan M3đối tượng với ID 1,2,3,4,5. Khi bạn tìm nạp M3các đối tượng được liên kết cho từng M2đối tượng đó, nếu bạn sử dụng chọn có liên quan, đây là cách nó sẽ hoạt động.

Các bước:

  1. Tìm m21đối tượng.
  2. Truy vấn tất cả các M3đối tượng liên quan đến m21đối tượng có ID 1,2,3,4,5.
  3. Lặp lại điều tương tự cho m22đối tượng và tất cả các M2đối tượng khác .

Như chúng ta có cùng một 1,2,3,4,5ID cho cả m21, m22đối tượng, nếu chúng tôi sử dụng tùy chọn select_related, nó sẽ truy vấn DB hai lần cho các ID cùng mà đã được lấy.

Thay vào đó, nếu bạn sử dụng prefetch_related, khi bạn cố gắng lấy M2các đối tượng, nó sẽ ghi chú tất cả các ID mà các đối tượng của bạn trả về (Lưu ý: chỉ ID) trong khi truy vấn M2bảng và như bước cuối cùng, Django sẽ thực hiện truy vấn vào M3bảng với tập hợp tất cả các ID mà các M2đối tượng của bạn đã trả về. và nối chúng với M2các đối tượng bằng Python thay vì cơ sở dữ liệu.

Bằng cách này, bạn chỉ truy vấn tất cả các M3đối tượng một lần để cải thiện hiệu suất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.