--- tags: Python, Django --- # Django ORM 效能優化 ## 1. queryset 資訊 + 如果不需要 queryset 的資料只需要知道queryset 有沒有資料,可以用exists() + 不要直接把queryset 放在if的條件裡 ```python= # Don't waste a query if you are using the queryset books = Book.objects.filter(..) if books: do_stuff_with_books(books) # If you aren't using the queryset use exist books = Book.objects.filter(..) if books.exists(): do_some_stuff() # But never if Book.objects.filter(..): do_some_stuff()` ``` + 要知道長度可以用 quryset.count(),要用到queryset資料可以用len(),不要把queryset查詢式放進 len()裡面 ```python= # Don't waste a query if you are using the queryset books = Book.objects.filter(..) if len(books) > 5: do_stuff_with_books(books) # If you aren't using the queryset use count books = Book.objects.filter(..) if books.count() > 5: do_some_stuff() # But never if len(Book.objects.filter(..)) > 5: do_some_stuff() ``` ## 2. 只取需要的部分 + queryset.values() 將指定欄位取出為字典 + queryset.values_list() 將指定欄位取出為元組 ```python # Retrieve values as a dictionary >>> Book.objects.values('title', 'author__name') <QuerySet [{'author__name': u'Nikolai Gogol', 'title': u'The Overcoat'}, {'author__name': u'Leo Tolstoy', 'title': u'War and Peace'}]> # Retrieve values as a tuple >>> Book.objects.values_list('title', 'author__name') <QuerySet [(u'The Overcoat', u'Nikolai Gogol'), (u'War and Peace', u'Leo Tolstoy')]> >>> Book.objects.values_list('title') <QuerySet [(u'The Overcoat',), (u'War and Peace',)]> # With one value, it is easier to flatten the list >>> Book.objects.values_list('title', flat=True) <QuerySet [u'The Overcoat', u'War and Peace']> ``` ## 3. 迭代處理 ```python # 這個寫法比較慢,因為一次把資料全載入記憶體 for book in Books.objects.all(): do_stuff(book) # 這個比較好,每次要用才載入下一筆資料 for book in Books.objects.all().iterator(): do_stuff(book) ``` ## 4. 關係問題 select_related() => 根據foreign key 預先快取相關資料,在資料庫層用了JOIN語法把資料組合 (1 to 1, foreignkey) select_related() 可以跟 filter()一起搭配使用, 所有資料必須在同一資料庫 prefetch_related為每一個關係使用了單獨的查詢,然後在 python層把資料 JOIN,所以不會被限制(可以查詢多對多關係) 針對小資料集,一對一關係可以用 select_related 多對多或是大資料及可以用 prefetch_related 參考資料: https://kknews.cc/zh-tw/code/egge6vn.html ```python= >>> Author.objects.count() 20 >>> Book.objects.count() 100 # 下面程式總共 101 次查詢 # 一次找book還有每個作者一次 books = Book.objects.all() for book in books: do_stuff(book.title, book.author.name) # 這樣寫有21次查詢 # 一次作者還有20本書各自查一次作者 authors = Author.objects.all() for author in authors: do_stuff_with_books(author.name, author.books.all()) # 這只用到一次查詢 # 所有書的作者已經被prefetch了 book = Book.objects.selected_related('author').all() for book in books: do_stuff(book.title, book.author) # 這只用到一次查詢 # 所有作者的書已經被prefetch了 authors = Author.objects.prefetch_related('books').all() for author in authors: do_stuff_with_books(author.name, author.books.all()) ``` 如果要用queryset.filter()的話prefetch data會失效 這時候要改用Prefetch 物件 ## 5. 加入 index 可參考官方文件 ```python from django.db import models class Customer(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) class Meta: indexes = [ models.Index(fields=['last_name', 'first_name']), models.Index(fields=['first_name'], name='first_name_idx'), ] ``` 經過 index 標注的欄位找起來會比較快(15倍速度) 缺點: 要多花很多空間存資料,因為 index就是新建一張表存指定的欄位 每多一個欄位的index就多一張表,很耗空間 所以只要隊常讀取的欄位設定 index就好