一、QuerySet對象
Django的ORM中存在查詢集的概念。
查詢集,也稱查詢結(jié)果集,即QuerySet,表示從數(shù)據(jù)庫中獲取的對象集合。
當(dāng)調(diào)用如下過濾器方法時,Django會返回查詢集(與列表類似,但不是簡單的列表):
all():返回所有數(shù)據(jù)。
filter():返回滿足條件的數(shù)據(jù)。
exclude():返回滿足條件之外的數(shù)據(jù)。
order_by():對結(jié)果進(jìn)行排序。
1、可切片
可以使用Python 的切片語法來限制查詢集記錄的數(shù)目,它等同于SQL 的LIMIT 和OFFSET 子句 。
res = Book.objects.all()[:5] # limit 5
print(res) # <QuerySet [<Book: book1>, ..., <Book: book5>]>
res = Book.objects.all()[5:7] # LIMIT 2 OFFSET 5,偏移5所以從6開始,到7結(jié)束即limit 2
print(res) # <QuerySet [<Book: book6>, <Book: book7>]>
不支持負(fù)的索引(例如Book.objects.all()[-1])。
強(qiáng)調(diào):不能把QuerySet單純地當(dāng)成python中的列表,它們還是有區(qū)別的。
2、可迭代
books = Book.objects.all()
for book in books:
print(book.name)
3、惰性查詢
查詢集 是惰性執(zhí)行的 —— 創(chuàng)建查詢集不會帶來任何數(shù)據(jù)庫的訪問。你可以將過濾器保持一整天,直到查詢集 需要求值時,Django 才會真正運(yùn)行這個查詢。
query_res = Book.objects.all() # 不會查詢數(shù)據(jù)庫,終端也并無sql日志打印
print(query_res) # 此時才會查詢數(shù)據(jù)庫,在開啟sql日志功能后,可以在終端看到執(zhí)行的原生sql
for book in query_res:
print(book.name) # 此時會再次查詢數(shù)據(jù)庫
一般來說,只有在“請求”查詢集 的結(jié)果時才會到數(shù)據(jù)庫中去獲取它們。當(dāng)你確實(shí)需要結(jié)果時,查詢集 通過訪問數(shù)據(jù)庫來求值
4、緩存機(jī)制
每個查詢集都包含一個緩存來最小化對數(shù)據(jù)庫的訪問。理解它是如何工作的將讓你編寫最高效的代碼。
在一個新創(chuàng)建的查詢集中,緩存為空。
當(dāng)我們首次對查詢集進(jìn)行求值時--同時發(fā)生數(shù)據(jù)庫查詢 ,Django 將查詢的結(jié)果保存到查詢集的緩存中并返回明確請求的結(jié)果。接下來對該查詢集 的求值將重用緩存的結(jié)果。
請牢記這個緩存行為,因?yàn)閷?em>查詢集使用不當(dāng)?shù)脑?,它會坑你的?/p>
例如,下面的兩條語句查出的都是所有的書籍,但是每條都創(chuàng)建了新的查詢集,然后各自對各自的查詢集求值,這存在兩大問題
? 1、相同的數(shù)據(jù)庫查詢將執(zhí)行兩次,顯然倍增了你的數(shù)據(jù)庫負(fù)載。
? 2、還有可能兩個結(jié)果列表并不包含相同的數(shù)據(jù)庫記錄,因?yàn)樵趦纱握埱笃陂g有可能有Article被添加進(jìn)來或刪除掉。
print([book.name for book in Book.objects.all()])
print([book.price for book in Book.objects.all()])
為了避免上述問題,只需保存查詢集并重新使用它,如下
# 1、先保存結(jié)果集
query_res = Book.objects.all()
# 2、然后再對結(jié)果集進(jìn)行求值
print([book.name for book in query_res]) # 首次對查詢集求值,會查詢數(shù)據(jù)庫并緩存
print([book.price for book in query_res]) # 命中緩存,無需查詢數(shù)據(jù)庫
5、何時查詢集不會被緩存?
1、只有在對查詢集求值后才會緩存
如下操作都算是在對查詢集求值,它們會使得全部的查詢集被求值并填充到緩存中:
[book for book in query_res]
bool(query_res)
any_obj in query_res
list(query_res)
for obj in query_res:
print('哪怕只遍歷一次,也算是對查詢集求值了,會緩存Book.objects.all()的所有結(jié)果')
break
2、單純的打印結(jié)果集不算是對查詢集求值,所以不會被緩存,每次打印都會引發(fā)新的數(shù)據(jù)庫查詢
query_res = Book.objects.all()
print(query_res) # 查詢數(shù)據(jù)庫
print(query_res) # 查詢數(shù)據(jù)庫
3、使用切片或索引來限制查詢集也不算是對查詢集求值,所以也不會被緩存
query_res = Book.objects.all()
print(query_res[5]) # 查詢數(shù)據(jù)庫
print(query_res[3:5]) # 查詢數(shù)據(jù)庫
所以,我們可以先對查詢集求值,緩存好數(shù)據(jù)之后再進(jìn)行上述2和3的操作
query_res = Book.objects.all()
123 in query_res # 對查詢集求值,會查詢數(shù)據(jù)庫并緩存
print(query_res) # 命中緩存
print(query_res[5]) # 命中緩存
print(query_res[3:5]) # 命中緩存
6、exists()與iterator()方法
簡單的使用if語句進(jìn)行判斷也會完全執(zhí)行整個queryset并且把數(shù)據(jù)放入cache,如下
query_res = Book.objects.all()
if query_res: # 查詢數(shù)據(jù)庫并緩存
print('ok')
print(query_res) # 命中緩存
若僅僅只需要判斷是否存在數(shù)據(jù),那么再把所有數(shù)據(jù)集合都查詢回來緩存將會極大地降低效率,此時我們可以使用exists(),只拿回來一條來判斷是否存在即可,需要拿所有,當(dāng)然也不會緩存
query_res = Book.objects.all()
if query_res.exists(): # 查詢數(shù)據(jù)庫,但不緩存
# SELECT (1) AS `a` FROM `app01_book` LIMIT 1
print('ok')
print(query_res) # 無法命中任何緩存,需要再次查詢數(shù)據(jù)庫
當(dāng)queryset非常巨大時,cache會成為問題。
處理成千上萬的記錄時,將它們一次裝入內(nèi)存是很浪費(fèi)的。更糟糕的是,巨大的queryset可能會鎖住系統(tǒng) 進(jìn)程,讓你的程序?yàn)l臨崩潰。要避免在遍歷數(shù)據(jù)的同時產(chǎn)生queryset cache,可以使用iterator()方法 來獲取數(shù)據(jù),iterator并不會產(chǎn)生緩存,處理完數(shù)據(jù)后就丟棄了,要想重新獲取數(shù)據(jù)得重新拿到iterator(),如下所示。
books = Book.objects.all().iterator()
# iterator()可以一次只從數(shù)據(jù)庫獲取少量數(shù)據(jù),這樣可以節(jié)省內(nèi)存
for obj in books:
print(obj.name)
#強(qiáng)調(diào):再次遍歷沒有打印,因?yàn)榈饕呀?jīng)在上一次遍歷(next)到最后一次了,沒得遍歷了
for obj in books:
print(obj.name)
注意:使用iterator()方法來防止生成cache,意味著遍歷同一個queryset時會重復(fù)執(zhí)行查詢。所以使用iterator()的時候要當(dāng)心,確保你的代碼在操作一個大的queryset時沒有重復(fù)執(zhí)行查詢。
總結(jié):
queryset的cache是用于減少程序?qū)?shù)據(jù)庫的查詢,在通常的使用下會保證只有在需要的時候才會查詢數(shù)據(jù)庫。 使用exists()和iterator()方法可以優(yōu)化程序?qū)?nèi)存的使用。不過,由于它們并不會生成queryset cache,可能 會造成額外的數(shù)據(jù)庫查詢。
二、跨表查詢優(yōu)化
select_related()
(1)簡單使用
對于一對一字段(OneToOneField)和外鍵字段(ForeignKey),可以使用select_related 來對QuerySet進(jìn)行優(yōu)化,select_related底層就是鏈表操作
簡單說,在對QuerySet使用select_related()函數(shù)后,Django會獲取相應(yīng)外鍵對應(yīng)的對象,從而在之后需要的時候不必再查詢數(shù)據(jù)庫了,例如
res = Book.objects.all()
for obj in res:
print(obj.publish.name) # 每次執(zhí)行該行代碼都會觸發(fā)新的sql執(zhí)行,效率極低
"""
底層會先去book表里查出所有的書籍id
然后每次for循環(huán)都會拿著一個書籍的id去publish表里查到對應(yīng)出版社的名字
"""
# 優(yōu)化
res = Book.objects.select_related('publish')
for obj in res:
print(obj.publish.name)
"""
底層會先將Book與Publish對應(yīng)的兩張表連接成一張大表,然后一次性將大表的所有數(shù)據(jù)一次性封裝給查詢出來的對象
此時對象無論是點(diǎn)擊book表的數(shù)據(jù)還是publish表的數(shù)據(jù)都無需再走額外的數(shù)據(jù)庫查詢了
"""
for obj in res:
print(obj.publish.city)
"""
第二次for循環(huán)直接使用上述緩存即可
"""
但需要注意的是:select_related()括號內(nèi)只能放外鍵Foreignkey字段
因?yàn)橐粚Χ?、一對一關(guān)系均使用ForeignKey,而多對多則不是,所以select_related不支持多對多關(guān)系,若想優(yōu)化多對多的查詢請看下一小節(jié) (2)多外鍵查詢
如果一個模型中存在多個ForeignKey字段
res = Book.objects.select_related("publish")
此時我們查詢publish相關(guān)的時候,不會重復(fù)查詢,如下
for obj in res:
print(obj.publish.name)
for obj in res:
print(obj.publish.city)
但如果我們查詢的是Book內(nèi)的其他ForeignKey字段,還是會重新查詢
for obj in res:
print(obj.other_fk.attr)
# 解決方案就是
res = Book.objects.select_related("publish","other_fk")
或者使用django1.7之后支持的鏈?zhǔn)讲僮?res = Book.objects.select_related("publish").select_related("detail")
(2)深層查詢
對于跨越了n張表的深層查詢, 依然需要查詢多次,例如下述代碼依然需要重復(fù)查詢兩次
article=models.Article.objects.select_related("blog").get(nid=1)
print(article.blog.user.username) # 需要重復(fù)兩次進(jìn)行查詢
這是因?yàn)榈谝淮尾樵儧]有query到userInfo表,所以,修改如下:
article=models.Article.objects.select_related("blog__user").get(nid=1)
print(article.blog.user.username)
(3)總結(jié)
1、select_related主要針一對一和多對一關(guān)系進(jìn)行優(yōu)化。
2、select_related使用SQL的JOIN語句進(jìn)行優(yōu)化,通過減少SQL查詢的次數(shù)來進(jìn)行優(yōu)化、提高性能。
3、可以通過可變長參數(shù)指定需要select_related的字段名。也可以通過使用雙下劃線“__”連接字段名來實(shí)現(xiàn)指定的遞歸查詢。
4、沒有指定的字段不會緩存,沒有指定的深度不會緩存,如果要訪問的話Django會再次進(jìn)行SQL查詢。
5、也可以通過depth參數(shù)指定遞歸的深度,Django會自動緩存指定深度內(nèi)所有的字段。如果要訪問指定深度外的字段,Django會再次進(jìn)行SQL查詢。
6、也接受無參數(shù)的調(diào)用,Django會盡可能深的遞歸查詢所有的字段。但注意有Django遞歸的限制和性能的浪費(fèi)。
7、Django >= 1.7,鏈?zhǔn)秸{(diào)用的select_related相當(dāng)于使用可變長參數(shù)。Django < 1.7,鏈?zhǔn)秸{(diào)用會導(dǎo)致前邊的select_related失效,只保留最后一個。
prefetch_related()
對于多對多字段(ManyToManyField)和一對多字段,可以使用prefetch_related()來進(jìn)行優(yōu)化,prefetch_related()的底層就是子查詢,在使用時select_related與prefetch_related是沒有差別的,但是底層是有差別的
針對一對多字段
# 效果與select_related()一樣
res = Book.objects.prefetch_related('publish')
for obj in res:
print(obj.publish.name)
for obj in res:
print(obj.publish.city)
針對多對多字段
books = Book.objects.prefetch_related('authors') # 底層就是inner join
for book in books:
for author in book.authors.all():
print(book.name,author.name)
注意
鏈表與子查詢的效率不一定誰高誰低
鏈表有可能遇到很多張表,連在一起,會在耗費(fèi)很多時間在鏈表上
而子查詢,每次都一張表,分多步運(yùn)行,不需要鏈表,但步驟多了,有可能影響效率
通常情況下:
數(shù)據(jù)量少的話,建議用select_related
數(shù)據(jù)量比較多,建議用prefetch_related
三、only與defer
1、only
下例中,因?yàn)閞es是.all()得到的結(jié)果集合,所以obj.任意書籍對象自己的屬性,均不再走數(shù)據(jù)庫
res = Book.objects.all()
for obj in res:
print(obj.name) # 不走數(shù)據(jù)庫查詢
print(obj.price) # 不走數(shù)據(jù)庫查詢
因?yàn)樯厦媸?all()所以拿到的結(jié)果集必然數(shù)據(jù)量大,如果我們只想要書的名字,可以使用.only()
res = Book.objects.only('name')
for obj in res:
print(obj.name) # .name不走數(shù)據(jù)庫 .其他字段會重新走數(shù)據(jù)庫查詢
2、defer
defer與only剛好相反,除了defer指定的屬性外,其他都不需要查數(shù)據(jù)庫
res = Book.objects.defer('name')
for obj in res:
print(obj.price) # 除了.name之外,點(diǎn)出的屬性都不需要走數(shù)據(jù)庫
# print(res) # 打印res,會觸發(fā)多條sql的執(zhí)行來拿到所有的結(jié)果集,因?yàn)樵L問了除了name之外的屬性
四、整體插入
創(chuàng)建對象時,盡可能使用bulk_create()來減少SQL查詢的數(shù)量。例如:
Entry.objects.bulk_create([
Entry(headline="Python 3.0 Released"),
Entry(headline="Python 3.1 Planned")
])
...更優(yōu)于:
Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")
注意該方法有很多注意事項(xiàng),所以確保它適用于你的情況。
這也可以用在ManyToManyFields中,所以:
my_band.members.add(me, my_friend)
...更優(yōu)于:文章來源:http://www.zghlxwxcb.cn/news/detail-519198.html
my_band.members.add(me)
my_band.members.add(my_friend)
...其中Bands和Artists具有多對多關(guān)聯(lián)。文章來源地址http://www.zghlxwxcb.cn/news/detail-519198.html
到了這里,關(guān)于Django之QuerySet對象與查詢優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!