我有一個基于 Django REST Framework 的專案,其中一個端點非常慢。原來這是一個 N 1 查詢問題,其中回傳的數百行中的每一行都會導致額外的查詢。端點不應該做任何連接或額外的查詢,只回傳一個表的內容,過濾一下。該表的模型包含一些外鍵,但不應該訪問任何參考,只有 ID 應該呈現為 JSON 并發送到客戶端。
一些試驗和錯誤表明是 DRFSerializer
導致了額外的查詢:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ["data_field_a", "data_field_b", "bar_code", "qux_id"]
當我bar_code
在序列化程式中注釋掉時,查詢變得與預期一樣快。令人驚訝的是qux_id
,即使該欄位與 基本相同bar_code
,也不會導致這樣的減速。我閱讀了https://www.django-rest-framework.org/api-guide/relations/以及其他內容,嘗試depth = 0
在Meta
課堂上進行設定。
另外,我知道使用select_related
orprefetch_related
基本上可以作為問題的快速解決方案,但嚴格來說,這種情況下不需要這些,因為實際上不需要連接或其他查詢,我試圖理解問題的根源加深了我的理解。
所以,我很好地掌握了減速的實際原因(序列化程式的額外查詢),但不太了解為什么序列化程式決定對一個欄位而不是另一個欄位進行這些查詢。我的問題是,我應該如何繼續除錯呢?我之前沒有 DRF 方面的經驗。
編輯:我被要求分享模型的代碼,所以我們開始吧。我洗掉了所有其他欄位,因為它們似乎與錯誤沒有任何關系;我仍然可以重現它:
class Foo(models.Model):
bar_code = models.ForeignKey(
Bar, to_field="bar_code", db_column="bar_code", on_delete=models.CASCADE
)
qux_id = models.ForeignKey(
Qux, to_field="qux_id", db_column="qux_id", on_delete=models.CASCADE
)
(編輯:后來證明,這兩者的區別在于該欄位是否是參考模型的主鍵。)
Edit2:甚至更小:
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ["bar_code"]
class Foo(models.Model):
bar_code = models.ForeignKey(
Bar, to_field="bar_code", db_column="bar_code", on_delete=models.CASCADE
)
uj5u.com熱心網友回復:
所以,我發現了我的答案,這就是我除錯它的方法。主要是三件事:
- 檢查序列化器的作用。 https://www.django-rest-framework.org/api-guide/relations/#inspecting-relationships
poetry run python manage.py shell
...
>>> from app.views import FooSerializer
>>> print(repr(FooSerializer()))
FooSerializer():
bar_code = SlugRelatedField(queryset=Bar.objects.all(), slug_field='unique_code')
這清楚地表明,它正在將該欄位視為參考,而不僅僅是一個簡單的值。這對一些 Django 開發人員來說可能很明顯,但對我來說肯定不是,因為我們試圖序列化的值是 on 欄位的值Foo
,而不是Bar
. 似乎它的邏輯類似于“Bar
使用外鍵獲取Bar
的唯一(非 PK)欄位。擁有 后Bar
,讀取該欄位。” 但這對我來說似乎違反直覺和不必要,因為我們已經有了訪問的價值Bar
!
- 對問題進行最小限度的再現:https ://github.com/golddranks/drf-demo
這有助于我注意到與問題相關的內容和不相關的內容。玩復制品也讓我注意到問題不僅在于 .,還在于Serializer
通常訪問該bar_code
欄位。我意識到我不明白如何在不觸發獲取整個參考模型的情況下訪問該欄位的基礎值。玩了一圈,我還發現 whenbar_code
是 的主鍵,Bar
其實很聰明,不用加載Bar
,直接訪問 的Foo
外鍵值!
- 用新建立的知識四處詢問。
一位朋友向我發送了一個拉取請求,該請求顯示了如何訪問外鍵欄位的基礎值:訪問欄位為bar_code_id
,并附加后綴_id
。令我驚訝的是,資料庫表沒有這樣的列!根據我從檔案中收集的資訊(https://docs.djangoproject.com/en/dev/ref/models/fields/#database-representation),我認為在這種情況下以這種方式訪問??底層欄位是可能的Django 自動創建了一個底層 ID 欄位,但在這種情況下它沒有,我們手動創建它,它當然沒有后綴_id
!然而bar_code_id
,作業并設法在不觸發加載的情況下獲取基礎值Bar
!
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/491899.html
標籤:django 调试 序列化 django-rest-framework