Django筆記二十八之資料庫查詢優化彙總

来源:https://www.cnblogs.com/hunterxiong/archive/2023/04/22/17343746.html
-Advertisement-
Play Games

本文首發於公眾號:Hunter後端 原文鏈接:Django筆記二十八之資料庫查詢優化彙總 這一篇筆記將從以下幾個方面來介紹 Django 在查詢過程中的一些優化操作,有一些是介紹如何獲取 Django 查詢轉化的 sql 語句,有一些是理解 QuerySet 是如何獲取數據的。 以下是本篇筆記目錄: ...


本文首發於公眾號:Hunter後端
原文鏈接:Django筆記二十八之資料庫查詢優化彙總

這一篇筆記將從以下幾個方面來介紹 Django 在查詢過程中的一些優化操作,有一些是介紹如何獲取 Django 查詢轉化的 sql 語句,有一些是理解 QuerySet 是如何獲取數據的。

以下是本篇筆記目錄:

  1. 性能方面
  2. 使用標準的資料庫優化技術
  3. 理解 QuerySet
  4. 操作儘量在資料庫中完成而不是在記憶體中
  5. 使用唯一索引來查詢單個對象
  6. 如果知道需要什麼數據,那麼就立刻查出來
  7. 不要查詢你不需要的數據
  8. 使用批量的方法

1、性能方面

1. connection.queries

前面我們介紹過 connection.queries 的用法,比如我們執行了一條查詢之後,可以通過下麵的方式查到我們剛剛的語句和耗時

>>> from django.db import connection
>>> connection.queries
[{'sql': 'SELECT polls_polls.id, polls_polls.question, polls_polls.pub_date FROM polls_polls',
'time': '0.002'}]

僅僅當系統的 DEBUG 參數設為 True,上述命令才可生效,而且是按照查詢的順序排列的一個數組

數組的每一個元素都是一個字典,包含兩個 Key:sql 和 time

sql 為查詢轉化的查詢語句
time 為查詢過程中的耗時

因為這個記錄是按照時間順序排列的,所以 connection.queries[-1] 總能查詢到最新的一條記錄。

多資料庫操作

如果系統用的是多個資料庫,那麼可以通過 connections['db_alias'].queries 來操作,比如我們使用的資料庫的 alias 為 user:

>>> from django.db import connections
>>> connections['user'].queries

如果想清空之前的記錄,可以調用 reset_queries() 函數:

from django.db import reset_queries
reset_queries()

2. explain

我們也可以使用 explain() 函數來查看一條 QuerySet 的執行計劃,包括索引以及聯表查詢的的一些信息

這個操作就和 MySQL 的 explain 是一樣的。

>>> print(Blog.objects.filter(title='My Blog').explain())
Seq Scan on blog  (cost=0.00..35.50 rows=10 width=12)
  Filter: (title = 'My Blog'::bpchar)

也可以加一些參數來查看更詳細的信息:

>>> print(Blog.objects.filter(title='My Blog').explain(verbose=True, analyze=True))
Seq Scan on public.blog  (cost=0.00..35.50 rows=10 width=12) (actual time=0.004..0.004 rows=10 loops=1)
  Output: id, title
  Filter: (blog.title = 'My Blog'::bpchar)
Planning time: 0.064 ms
Execution time: 0.058 ms

之前在使用 Django 的過程中還使用到一個叫 silk 的工具,它可以用來分析一個介面各個步驟的耗時,有興趣的可以瞭解一下。

2、使用標準的資料庫優化技術

資料庫優化技術指的是在查詢操作中 SQL 底層本身的優化,不涉及 Django 的查詢操作

比如使用 索引 index,可以使用 Meta.indexes 或者欄位里的 Field.db_index 來添加索引

如果頻繁的使用到 filter()、exclude()、order_by() 等操作,建議為其中查詢的欄位添加索引,因為索引能幫助加快查詢

3、理解 QuerySet

1. 理解 QuerySet 獲取數據的過程

1) QuerySet 的懶載入

一個查詢的創建並不會訪問資料庫,直到獲取這條查詢語句的具體數據的時候,系統才會去訪問資料庫:

>>> q = Entry.objects.filter(headline__startswith="What")  # 不訪問資料庫
>>> q = q.filter(pub_date__lte=datetime.date.today())  # 不訪問資料庫
>>> q = q.exclude(body_text__icontains="food")  # 不訪問資料庫
>>> print(q)  # 訪問資料庫

比如上面四條語句,只有最後一步,系統才會去查詢資料庫。

2) 數據什麼時候被載入

迭代、使用步長分片、使用len()函數獲取長度以及使用list()將QuerySet 轉化成列表的時候數據才會被載入

這幾點情況在我們的第九篇筆記中都有詳細的描述。

3) 數據是怎麼被保存在記憶體中的

每一個 QuerySet 都會有一個緩存來減少對資料庫的訪問操作,理解其中的運行原理能幫助我們寫出最有效的代碼。

當我們創建一個 QuerySet 的之後,並且數據第一次被載入,對資料庫的查詢操作就發生了。

然後 Django 會保存 QuerySet 查詢的結果,並且在之後對這個 QuerySet 的操作中會重覆使用,不會再去查詢資料庫。

當然,如果理解了這個原理之後,用得好就OK,否則會對資料庫進行多次查詢,造成性能的浪費,比如下麵的操作:

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

上面的代碼,同樣一個查詢操作,系統會查詢兩遍資料庫,而且對於數據來說,兩次的間隔期之間,Entry 表可能的某些資料庫可能會增加或者被刪除造成數據的不一致。

為了避免此類問題,我們可以這樣復用這個 QuerySet :

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # 查詢資料庫
>>> print([p.pub_date for p in queryset]) # 從緩存中直接使用,不會再次查詢資料庫

這樣的操作系統就只執行了一遍查詢操作。

使用數組的切片或者根據索引(即下標)不會緩存數據

QuerySet 也並不總是緩存所查詢的結果,如果只是獲取一個 QuerySet 部分數據,會查詢有是否這個 QuerySet 的緩存
有的話,則直接從緩存中獲取數據,沒有的話,後續也不會將這部分數據緩存到系統中。

舉個例子,比如下麵的操作,在緩存整個 QuerySet 數據前,查詢一個 QuerySet 的部分數據時,系統會重覆查詢資料庫:

>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # 查詢資料庫
>>> print(queryset[5]) # 再次查詢資料庫

而在下麵的操作中,整個 QuerySet 都被提前獲取了,那麼根據索引的下標獲取數據,則能夠從緩存中直接獲取數據:

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # 查詢資料庫
>>> print(queryset[5]) # 使用緩存
>>> print(queryset[5]) # 使用緩存

如果一個 QuerySet 已經緩存到記憶體中,那麼下麵的操作將不會再次查詢資料庫:

>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

2. 理解 QuerySet 的緩存

除了 QuerySet 的緩存,單個 model 的 object 也有緩存的操作。

我們這裡簡單理解為外鍵和多對多的關係。

比如下麵外鍵欄位的獲取,blog 是 Entry 的一個外鍵欄位:

>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # Blog 的實例被查詢資料庫獲得
>>> entry.blog   # 第二次獲取,使用緩存信息,不會查詢資料庫

而多對多關係的獲取每次都會被重新去資料庫獲取數據:

>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # 查詢資料庫
>>> entry.authors.all()   # 再次查詢資料庫

當然,以上的操作,我們都可以通過 select_related() 和 prefetch_related() 的方式來減少資料庫的訪問,這個的用法在前面的筆記中有介紹。

4、操作儘量在資料庫中完成而不是在記憶體中

舉幾個例子:

  1. 在大多數查詢中,使用 filter() 和 exclude() 在資料庫中做過濾,而不是在獲取所有數據之後在 Python 里的 for 迴圈里篩選數據
  2. 在同一個 model 的操作中,如果有涉及到其他欄位的操作,可以用到 F 表達式
  3. 使用 annotate 函數在資料庫中做聚合(aggregate)的操作

如果某些查詢比較複雜,可以使用原生的 SQL 語句,這個操作也在前面有過一篇完整的筆記介紹過

5、使用唯一索引來查詢單個對象

在使用 get() 來查詢單條數據的時候,有兩個理由使用唯一索引(unique)或 普通索引(db_index)

一個是基於資料庫索引,查詢會更快,

另一個是如果多條數據都滿足查詢條件,查詢會慢得多,而在唯一索引的約束下則保證這種情況不會發生

所以使用下麵的 id 進行匹配 會比 headline 欄位匹配快得多,因為 id 欄位在資料庫中有索引且是唯一的:

entry = Entry.objects.get(id=10)

entry = Entry.objects.get(headline="News Item Title")

而下麵的操作可能會更慢:

entry = Entry.objects.get(headline__startswith="News")

首先, headline 欄位上沒有索引,會導致資料庫獲取速度慢

其次,查詢並不能保證只返回一個對象,如果匹配上來多個對象,且從資料庫中檢索並返回數百數千條記錄,後果會很嚴重,其實就會報錯,get() 能接受的返回只能是一個實例數據。

6、如果知道需要什麼數據,那麼就立刻查出來

能一次性查詢所有需要的相關的數據的話,就一次性查詢出來,不要在迴圈中做多次查詢,因為那樣會多次訪問資料庫

所以這就需要理解並且用到 select_related() 和 prefetch_related() 函數

7、不要查詢你不需要的數據

1. 使用 values() 和 values_list() 函數

如果需求僅僅是需要某幾個欄位的數據,可以用到的數據結構為 dict 或者 list,可以直接使用這兩個函數來獲取數據

2. 使用 defer() 和 only()

如果明確知道只需要,或者不需要什麼欄位數據,可以使用這兩個方法,一般常用在 textfield 上,避免載入大數據量的 text 欄位

3. 使用 count()

如果想要獲取總數,使用 count() 方法,而不是使用 len() 來操作,如果數據有一萬條,len() 操作會導致這一萬條數據都載入到記憶體里,然後計數。

4. 使用 exists()

如果僅僅是想查詢數據是否至少存在一條可以使用 if QuerySet.exists() 而不是 if queryset 的形式

5. 使用 update() 和 delete()

能夠批量更新和刪除的操作就使用批量的方法,挨個去載入數據,更新數據,然後保存是不推薦的

6. 直接使用外鍵的值

如果需要外鍵的值,直接調用早就在這個 object 中的欄位,而不是載入整個關聯的 object 然後取其主鍵id

比如推薦:

entry.blog_id

而不是:

entry.blog.id

7. 如果不需要排序的結果,就不要order_by()

每一個欄位的排序都是資料庫的操作需要額外消耗性能的,所以如果不需要的話,儘量不要排序

如果在 Meta.ordering 中有一個預設的排序,而你不需要,可以通過 order_by() 不添加任何參數的方法來取消排序

為資料庫添加索引,可以幫助提高排序的性能

8、使用批量的方法

1. 批量創建

對於多條 model 數據的創建,儘可能的使用 bulk_create() 方法,這是要優於挨個去 create() 的

2. 批量更新

bulk_update 方法也優於挨個數據在 for 迴圈中去 save()

3. 批量 insert

對於 ManyToMany 方法,使用 add() 方法的時候添加多個參數一次性操作比多次 add 要好

my_band.members.add(me, my_friend)

要優於:

my_band.members.add(me)
my_band.members.add(my_friend)

4. 批量 remove

當去除 ManyToMany 中的數據的時候,也是能一次性操作就一次性操作:

my_band.members.remove(me, my_friend)

要好於:

my_band.members.remove(me)
my_band.members.remove(my_friend)

如果想獲取更多後端相關文章,可掃碼關註閱讀:
image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • chatPDF或者chatGPT的界面挺簡潔的,就是一個左側的列表以及右側的對話列表,現在使用css實現這樣的佈局 充分運用了flex佈局方式實現,左右分欄,以及對話形式展示效果 下麵是效果圖: 在手機設備看就隱藏左側,右側100%適應 下麵就是html和css的佈局代碼 <style> .chat ...
  • 一、優缺點 ActiveMQ 官網地址:http://activemq.apache.org/ - 官網介紹 Apache ActiveMQ是最流行的開源、多協議、基於Java的消息代理。它支持行業標準協議,因此用戶可以從多種語言和平臺的客戶端選擇中獲益。從JavaScript、C、C++、Pyth ...
  • 圖像金字塔 簡單來說就是 自下而上圖像一步一步縮小 1 高斯金字塔(涉及高斯分佈) 向下採樣(縮小,對金字塔來說是自下向上) 第一步: 高斯濾波去噪 第二部:將偶數行和列去掉 向上採樣(放大,對金字塔來說是自上向下) 第一步:在每個方向上擴大兩倍,新增的行和列填充0 第二步:利用之前同樣的內核進行捲 ...
  • 8.1 線程簡介 1 、多任務 現實生活中多件事一起作。 在程式中是指在一個系統中可以同時進行多個進程,即有多個單獨運行的任務,每一個任務對應一個進程。 每一個進程都有一段專用的記憶體區域,即使是多次啟動同一段程式產生不同的進程也是如此。 2、多線程 Java 給多線程編程提供了內置的支持。 一條線程 ...
  • Canny檢測的流程 Canny檢測主要是用於邊緣檢測 1)使用高斯濾波器,以平滑圖像,濾除雜訊。 2)計算圖像中每個像素點的梯度強度和方向。 3)應用非極大值(Non-Maximum Suppression)抑制,以消除邊緣檢測帶來的雜散響應 4)應用雙閾值(Double-Threshold)檢測 ...
  • 題目描述 輸入年份和月份,輸出這一年的這一月有多少天。需要考慮閏年。 輸入格式 輸入兩個正整數,分別表示年份 $y$ 和月數 $m$,以空格隔開。 輸出格式 輸出一行一個正整數,表示這個月有多少天。 樣例 #1 樣例輸入 #1 1926 8 樣例輸出 #1 31 樣例輸入 #2 2000 2 樣例輸 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇對 VLD 源碼包中的各文件用途做個概述。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. 整體概覽 以 vld2.5.1 版本為例,下載源碼 後,根目錄下一共 5 個文件夾:.teamcity、lib、mfc_detect、set ...
  • 創建一個成績單文件score.xlsx,將平時成績單.xlsx文件中對應班級工作表中學號和姓名列的內容寫入到score.xlsx中,並添加成績列,每個學生的成績採用隨機生成的一個分數填寫進去,最後統計所有學生的平均成績計算出來後,寫入到score.xlsx的最後一行最後一列之後的單元格中去。預想的步 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...