在本文中,您將了解 N 1 查詢、如何使用 AppSignal 檢測它們,以及如何修復它們以顯著加快 Django 應用程式的速度。
我們將從理論方面開始,然後轉向實際範例。實際範例將反映您在生產環境中可能遇到的場景。
讓我們開始吧!
N 1 查詢問題是與資料庫互動的 Web 應用程式中普遍存在的效能問題。這些查詢可能會導致嚴重的瓶頸,並且隨著資料庫的成長而加劇。
當您檢索物件集合,然後存取集合中每個項目的相關物件時,就會出現問題。例如,取得書籍清單需要單一查詢(1 個查詢),但存取每本書的作者會觸發每個項目的額外查詢(N 個查詢)。
在資料庫中建立或更新資料時也可能會出現 N 1 問題。例如,透過循環迭代來單獨建立或更新對象,而不是使用bulk_create() 或bulk_update() 等方法,可能會導致過多的查詢。
N 1 查詢效率極低,因為執行大量小查詢比將操作合併為更少、更大的查詢要慢得多,而且更耗費資源。
Django 的預設 QuerySet 行為可能會無意中導致 N 1 問題,特別是如果您不知道 QuerySet 是如何運作的。 Django 中的查詢集是惰性的,這表示在對查詢集求值之前不會執行任何資料庫查詢。
確保您擁有:
注意:此專案的原始程式碼可以在 appsignal-django-n-plus-one GitHub 儲存庫中找到。
我們將使用圖書管理網路應用程式。該 Web 應用程式旨在演示 N 1 查詢問題以及如何解決它。
先複製 GitHub 儲存庫的基礎分支:
$ git clone git@github.com:duplxey/appsignal-django-n-plus-one.git \ --single-branch --branch base && cd appsignal-django-n-plus-one
接下來,建立並啟動虛擬環境:
$ python3 -m venv venv && source venv/bin/activate
安裝要求:
(venv)$ pip install -r requirements.txt
遷移並填入資料庫:
(venv)$ python manage.py migrate (venv)$ python manage.py populate_db
最後,啟動開發伺服器:
(venv)$ python manage.py runserver
開啟您最喜歡的網頁瀏覽器並導航至 http://localhost:8000/books。 Web 應用程式應從資料庫傳回包含 500 本書的 JSON 清單。
Django 管理網站可透過 http://localhost:8000/admin 存取。管理員憑證是:
user: username pass: password
要在 Django 專案上安裝 AppSignal,請依照官方文件操作:
透過重新啟動開發伺服器確保一切正常:
$ git clone git@github.com:duplxey/appsignal-django-n-plus-one.git \ --single-branch --branch base && cd appsignal-django-n-plus-one
您的應用程式應自動向 AppSignal 發送演示錯誤。從此時起,您的所有錯誤都會傳送到 AppSignal。此外,AppSignal 將監控您應用程式的效能並偵測任何問題。
修正 N 1 查詢的先決條件是了解應用程式的資料庫架構。密切注意模型的關係:它們可以幫助您找出潛在的 N 1 問題。
Web 應用程式有兩個模型 - 作者和書籍 - 它們共享一對多 (1:M) 關係。這意味著每本書都與一個作者相關聯,而一個作者可以連結到多本書。
兩個模型都有一個 to_dict() 方法,用於將模型實例序列化為 JSON。最重要的是,Book 模型使用深度序列化(序列化書籍以及書籍的作者)。
模型在 books/models.py 中定義:
$ python3 -m venv venv && source venv/bin/activate
然後它們在 books/admin.py 中註冊 Django 管理站點,如下所示:
(venv)$ pip install -r requirements.txt
請注意,AuthorAdmin 使用 BookInline 在作者的管理頁面中顯示作者的書籍。
網路應用程式提供以下端點:
如果您正在執行開發網頁伺服器,則可以點擊上面的連結。
它們在 books/views.py 中定義如下:
(venv)$ python manage.py migrate (venv)$ python manage.py populate_db
太棒了,您現在知道網頁應用程式是如何運作的!
在下一節中,我們將對我們的應用程式進行基準測試,以使用 AppSignal 檢測 N 1 個查詢,然後修改程式碼以消除它們。
使用 AppSignal 檢測效能問題很容易。您所要做的就是像平常一樣使用/測試應用程式(例如,透過存取所有端點並驗證回應來執行最終用戶測試)。
當某個端點被命中時,AppSignal 將為其建立一份效能報告,並將所有相關存取分組在一起。每次訪問都將作為樣本記錄在端點的報告中。
首先,訪問您應用程式的所有端點以產生效能報告:
接下來,讓我們使用 AppSignal 儀表板來分析慢速端點。
導覽至您的 AppSignal 應用程式並選擇 效能 >側邊欄上的問題清單。然後按一下平均值 以平均回應時間降序對問題進行排序。
點擊最慢的端點(books/)查看其詳細資訊。
查看最新範例,我們可以看到該端點在 1090 毫秒內回傳回應。群組細分顯示 SQLite 需要 651 毫秒,而 Django 需要 439 毫秒。
這表示有問題,因為像這樣簡單的端點不應該花費那麼長的時間。
要獲取有關所發生事件的更多詳細信息,請選擇側邊欄中的範例,然後選擇最新範例。
向下捲動到事件時間軸以查看執行了哪些 SQL 查詢。
將滑鼠停留在 query.sql 文字上會顯示實際的 SQL 查詢。
執行了超過 1000 個查詢:
$ git clone git@github.com:duplxey/appsignal-django-n-plus-one.git \ --single-branch --branch base && cd appsignal-django-n-plus-one
這些是 N 1 個查詢的明顯標誌。第一個查詢取得一本書 (1),隨後的每個查詢取得該書的作者詳細資料 (N)。
要修復它,請導航至 books/views.py 並修改 book_list_view(),如下所示:
$ python3 -m venv venv && source venv/bin/activate
透過利用 Django 的 select_lated() 方法,我們在初始查詢中選擇附加的相關物件資料(即作者)。 ORM 現在將利用 SQL 連接,最終查詢將如下所示:
(venv)$ pip install -r requirements.txt
等待開發伺服器重新啟動並重新測試受影響的端點。
再次進行基準測試後,回應時間從 1090 減少到 45,查詢數量從 1024 減少到 2。分別提高了 24 倍和 512 倍。
接下來,讓我們來看看第二慢的端點(books/by-authors/)。
像我們在上一個步驟中所做的那樣使用儀表板來檢查端點的 SQL 查詢。您會注意到此端點有類似但不太嚴重的 N 1 模式。
這個端點的效能較不嚴重,因為 Django 夠聰明,可以快取頻繁執行的 SQL 查詢,也就是重複取得一本書的作者。查看官方文件以了解有關 Django 快取的更多資訊。
讓我們利用 books/views.py 中的 prefetch_lated() 來加速端點:
$ git clone git@github.com:duplxey/appsignal-django-n-plus-one.git \ --single-branch --branch base && cd appsignal-django-n-plus-one
在上一節中,我們使用 select_lated() 方法來處理一對一關係(每本書都有一個作者)。然而,在本例中,我們正在處理一對多關係(一個作者可以擁有多本書),因此我們必須使用 prefetch_lated()。
這兩種方法的區別在於 select_lated() 工作在 SQL 級別,而 prefetch_lated() 則在 Python 級別進行最佳化。後一種方法也可以用於多對多關係。
有關更多信息,請查看 Django 關於 prefetch_lated() 的官方文檔。
基準測試後,回應時間從 90 毫秒減少到 44 毫秒,查詢數量從 32 減少到 4。
在 Django 管理網站中發現 N 1 個查詢的工作原理類似。
首先,登入您的管理網站並產生績效報告(例如,建立一些作者或書籍,更新和刪除它們)。
接下來,導覽到您的 AppSignal 應用程式儀表板,這次由管理員過濾問題:
就我而言,兩個最慢的端點是:
我們無法對 /admin/login 做太多事情,因為它完全由 Django 處理,所以讓我們專注於第二個最慢的端點。檢查它會發現 N 1 查詢問題。每本書都會單獨取得作者。
要解決此問題,請重寫 BookInline 中的 get_queryset() 以在初始查詢中取得作者詳細資訊:
$ python3 -m venv venv && source venv/bin/activate
再次進行基準測試並驗證查詢數量是否有減少。
在這篇文章中,我們討論了使用 AppSignal 檢測和修復 Django 中的 N 1 個查詢。
利用您在這裡學到的知識可以幫助您大幅加快 Django Web 應用程式的速度。
要記住的兩個最重要的方法是 select_lated() 和 prefetch_lated()。第一個用於一對一關係,第二個用於一對多和多對多關係。
編碼愉快!
P.S.如果您想在 Python 文章發布後立即閱讀,請訂閱我們的 Python Wizardry 時事通訊,不錯過任何一篇文章!
以上是使用 AppSignal 在 Django 中尋找並修復 N ueries的詳細內容。更多資訊請關注PHP中文網其他相關文章!