django ORM 中 select_related 與 prefetch_related 的使用場景

kingron發表於2024-06-21

在常規models操作中,某些對於關聯表的查詢稍有不慎可能導致產生多次查詢,如:

# views.py
from . import models

def test(request):
    '''假設有一張user表,其關聯表為`usertype`,關聯欄位為`ut`
    現在想要遍歷user表每一條資料 
    並查詢其關聯欄位`ut`所關聯表的對應`title`欄位
    '''
    # SELECT * FROM User
    userlist = models.User.objects.all()
    for row in userlist:
        # SELECT title FROM usertype WHERE id=row.id
        print('row.ut.title')

上面這種查詢操作會造成不必要的多次查詢,因為當userlist = models.User.objects.all()執行時,取出來的資料包含了ut.id但並不包含ut.title,所以每次迴圈取值時都會去查詢關聯表的title欄位。對此可以透過select_related方法進行最佳化,該方法會將關聯表中的指定欄位一次查詢(即JOIN查詢),從而避免產生多次查詢:

def test(request):
    # SELECT * FROM User LEFT JOIN usertype on User.ut_id=usertype.id
    userlist = models.User.objects.all().select_related('ut')
    for row in userlist:
        ...

select_related可以接收多個引數用於對多個關聯表的連表查詢。注意如果不需要對第三張表進行操作的話,則應該避免使用select_related方法,因為連表查詢同樣消耗時間。


使用select_related方法雖然能提升效能,但終究做了一次連表查詢,在同等條件下,連表查詢效能是低於單表查詢的。所以prefetch_related就應運而生了。它會先執行子表的查詢,然後透過計算得出結果關鍵外來鍵的唯一值列表(可以理解為對所有關聯外來鍵進行set操作去除重複值),然後透過IN條件再從關聯表中查詢資料並放到記憶體中。示例如下:

models.UserInfo.objects.prefetch_related('ut')
# 先執行: SELECT * FROM UserInfo
# 透過計算獲取到所有使用者的使用者型別ID(ut的所有唯一值) [1, 2, 3]
# 最後執行: SELECT * FROM usertype WHERE id IN (1, 2, 3)

參考

  1. QuerySet API reference | Django 文件 | Django

相關文章