主頁 > 後端開發 > Django筆記二十八之資料庫查詢優化匯總

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

2023-04-23 07:33:12 後端開發

本文首發于公眾號: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

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550850.html

標籤:其他

上一篇:多執行緒(一)

下一篇:返回列表

標籤雲
其他(157847) Python(38092) JavaScript(25381) Java(17985) C(15215) 區塊鏈(8256) C#(7972) AI(7469) 爪哇(7425) MySQL(7137) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4557) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2430) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1959) Web開發(1951) HtmlCss(1919) python-3.x(1918) 弹簧靴(1913) C++(1910) xml(1889) PostgreSQL(1872) .NETCore(1854) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Django筆記二十八之資料庫查詢優化匯總

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

    uj5u.com 2023-04-23 07:33:12 more
  • 多執行緒(一)

    #一:什么是多執行緒 執行緒是作業系統能夠進行運算調度的最小單位;它被包含在行程之中,是行程中的實際運作單位。 多執行緒,是指從軟體或者硬體上實作多個執行緒并發執行的技術。具有多執行緒能力的計算機因有硬體支持而能夠在同一時間執行多于一個執行緒,進而提升整體處理性能。 簡單來說:執行緒是程式中一個單一的順序控制流程 ......

    uj5u.com 2023-04-23 07:33:06 more
  • Springboot 多實體負載均衡部署

    Springboot 多實體負載均衡部署 一、測驗代碼: 控制層測驗代碼: import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; @Controller @Re ......

    uj5u.com 2023-04-23 07:33:00 more
  • Go中回應式編程庫RxGo詳細介紹

    最近的專案用到了 RxGo ,因為之前從沒有接觸過,特意去學了學,特此記錄下。文章很多內容是復制了參考資料或者官方檔案。如果涉及侵權,請聯系洗掉,謝謝。 1、RxGo簡介 1.1 基礎介紹 RxGo是一個基于Go語言的回應式編程庫,它提供了一種簡單而強大的方式來處理異步事件流和資料流。RxGo的設計 ......

    uj5u.com 2023-04-23 07:32:39 more
  • Midjourney 提示詞工具(10 個國內外最好最推薦的)

    Midjourney,是一個革命性的基于人工智能的藝術生成器,可以從被稱為提示的簡單文本描述中生成令人驚嘆的影像。Midjourney已經迅速成為藝術家、設計師和營銷人員的首選工具(包括像我這樣根本不會設計任何東西的無能之輩)。 為了幫助你開始使用這個強大的工具,我們匯編了一份15個資源的清單,可以 ......

    uj5u.com 2023-04-23 07:32:23 more
  • C++的拓撲排序實作

    template<typename T = CString, typename _Data = https://www.cnblogs.com/shizhimofa/archive/2023/04/22/CString> struct Union_node//!< 節點 { Union_node() :nColor(0) {} std::vector<Union_node*> vecNodeSon; T ......

    uj5u.com 2023-04-23 07:32:18 more
  • Python基礎—conda使用筆記

    Python基礎—conda使用筆記 1. 環境配置 由于用conda管理虛擬環境真滴很方便,所以主要使用conda,就不單獨去裝Python了。 1.1. Miniconda3安裝 Miniconda3官網下載地址:Miniconda Miniconda3清華鏡像下載:清華鏡像-Miniconda ......

    uj5u.com 2023-04-23 07:32:02 more
  • python學習-學生資訊管理系統并打包exe

    在B站自學Python 站主:Python_子木 授課:楊淑娟 平臺: 馬士兵教育 python: 3.9.9 #python打包exe檔案 #安裝PyInstaller pip install PyInstaller #-F打包exe檔案,stusystem\stusystem.py到py的路徑, ......

    uj5u.com 2023-04-23 07:31:51 more
  • windows10下golang使用protobuf前奏

    1.更改代理(方便步驟3) 方法一: go env -w GOPROXY="https://goproxy.cn" 方法二:(非永久性,該方法對我有效) $env:GOPROXY="https://goproxy.cn" 注: http://mirrors.aliyun.com/goproxy/ 阿 ......

    uj5u.com 2023-04-23 07:31:46 more
  • 影像梯度

    影像梯度影像梯度計算的是影像變化的速度 對于影像的邊緣部分,其灰度值變化較大,梯度值也較大相反,對于影像中比較平滑的部分,其灰度值變化較小,相應的梯度值也較小。影像梯度計算需要求導數,但是影像梯度一般通過計算像素值的差來得到梯度的近似值(近似導數值)。(差分,離散) Sobel算子 1 #Sobel ......

    uj5u.com 2023-04-23 07:31:41 more