python--model進階


一、QuerySet

1.可切片

使用Python 的切片語法來限制查詢集記錄的數目 。它等同於SQL 的LIMIT 和OFFSET 子句。

 >>> Entry.objects. all ()[:5]      # (LIMIT 5)
>>> Entry.objects.all()[5:10]    # (OFFSET 5 LIMIT 5)

不支持負的索引(例如Entry.objects.all()[-1])。通常,查詢集 的切片返回一個新的查詢集 —— 它不會執行查詢。

2.可迭代

articleList=models.Article.objects.all()

for article in articleList:
    print(article.title)
View Code

3.惰性查詢

查詢集 是惰性執行的 —— 創建查詢集不會帶來任何數據庫的訪問。直到你用到了查詢集,才會訪問數據庫取值

queryResult=models.Article.objects.all() # not hits database   沒有訪問數據庫
 
print(queryResult) # hits database       此時才訪問數據庫
 
for article in queryResult:
    print(article.title)    # hits database

4.緩存機制

queryResult=models.Article.objects.all()      
print([a.title for a in queryResult])
print([a.create_time for a in queryResult])

此時只訪問了一次數據庫,第二次for取值時是從緩存中取的

但是緩存肯定是有數據限制的,不可能無限的存值,那就用到了下面的   iterator()  方法

5.  exists()與iterator()方法

 if queryResult.exists():
    #SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
        print("exists...")
當queryset非常巨大時,cache會成為問題。
處理成千上萬的記錄時,將它們一次裝入內存是很浪費的。更糟糕的是,巨大的queryset可能會鎖住系統 進程,讓你的程序瀕臨崩潰。要避免在遍歷數據的同時產生queryset cache,可以使用iterator()方法 來獲取數據,處理完數據就將其丟棄。

objs = Book.objects.all().iterator()
# iterator()可以一次只從數據庫獲取少量數據,這樣可以節省內存
for obj in objs:
    print(obj.title)
#BUT,再次遍歷沒有打印,因為迭代器已經在上一次遍歷(next)到最后一次了,沒得遍歷了
for obj in objs:
    print(obj.title)

當然,使用iterator()方法來防止生成cache,意味着遍歷同一個queryset時會重復執行查詢。所以使 #用iterator()的時候要當心,確保你的代碼在操作一個大的queryset時沒有重復執行查詢。

 

總結:

queryset的cache是用於減少程序對數據庫的查詢,在通常的使用下會保證只有在需要的時候才會查詢數據庫。 使用exists()和iterator()方法可以優化程序對內存的使用。不過,由於它們並不會生成queryset cache,可能 會造成額外的數據庫查詢。 

 

二、extra

extra(select=None, where=None, params=None, 
      tables=None, order_by=None, select_params=None)
有些情況下,Django的查詢語法難以簡單的表達復雜的 WHERE 子句,對於這種情況, Django 提供了 extra() QuerySet修改機制 — 它能在 QuerySet生成的SQL從句中注入新子句 extra可以指定一個或多個 參數,例如 select, where or tables. 這些參數都不是必須的,但是你至少要使用一個!要注意這些額外的方式對不同的數據庫引擎可能存在移植性問題.(因為你在顯式的書寫SQL語句),除非萬不得已,盡量避免這樣做 

參數之select
The select 參數可以讓你在 SELECT 從句中添加其他字段信息,它應該是一個字典,存放着屬性名到 SQL 從句的映射。
queryResult=models.Article
           .objects.extra(select={'is_recent': "create_time > '2017-09-05'"})
結果集中每個 Entry 對象都有一個額外的屬性is_recent, 它是一個布爾值,表示 Article對象的create_time 是否晚於2017-09-05.

練習:
# in sqlite:
    article_obj=models.Article.objects
              .filter(nid=1)
              .extra(select={"standard_time":"strftime('%%Y-%%m-%%d',create_time)"})
              .values("standard_time","nid","title")
    print(article_obj)
    # <QuerySet [{'title': 'MongoDb 入門教程', 'standard_time': '2017-09-03', 'nid': 1}]>

參數之where / tables
您可以使用where定義顯式SQL WHERE子句 - 也許執行非顯式連接。您可以使用tables手動將表添加到SQL FROM子句。 where和tables都接受字符串列表。所有where參數均為“與”任何其他搜索條件。
舉例來講:
queryResult=models.Article
           .objects.extra(where=['nid in (1,3) OR title like "py%" ','nid>2'])
 
        

三、整體插入

創建對象時,盡可能使用bulk_create()來減少SQL查詢的數量。例如:

Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Released"),
    Entry(headline="Python 3.1 Planned")
])

...更優於:

Entry.objects.create(headline="Python 3.0 Released")
Entry.objects.create(headline="Python 3.1 Planned")

注意該方法有很多注意事項,所以確保它適用於你的情況。

這也可以用在ManyToManyFields中,所以:

my_band.members.add(me, my_friend)

...更優於:

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

...其中Bands和Artists具有多對多關聯。

 

四、中介模型

class Article(models.Model):
    title = models.CharField(max_length=50, verbose_name='文章標題')
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
    )
----------多對多不用django創建的第三張表,用我自己定義的表,並且指定字段

這種情況我們可以在自己定義的第三張表中添加有用的字段

但是這樣的情況下,如果新增文章時,在選tag標簽后,在后端無法用 article_obj.tags.add()方法添加(其他的remove等方法都不管用),因為django不知道你是否新增了有用的字段,並且不知道是否給后添加的字段傳值。這就是中介模型。

解決辦法就是 :直接去自定義的第三張表 自己添加記錄

 

五、查詢優化   (  本質就是把表聯起來了)

表數據

class UserInfo(AbstractUser):
    """
    用戶信息
    """
    nid = models.BigAutoField(primary_key=True)
    nickname = models.CharField(verbose_name='昵稱', max_length=32)
    telephone = models.CharField(max_length=11, blank=True, null=True, unique=True, verbose_name='手機號碼')
    avatar = models.FileField(verbose_name='頭像',upload_to = 'avatar/',default="/avatar/default.png")
    create_time = models.DateTimeField(verbose_name='創建時間', auto_now_add=True)
 
    fans = models.ManyToManyField(verbose_name='粉絲們',
                                  to='UserInfo',
                                  through='UserFans',
                                  related_name='f',
                                  through_fields=('user', 'follower'))
 
    def __str__(self):
        return self.username
 
class UserFans(models.Model):
    """
    互粉關系表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey(verbose_name='博主', to='UserInfo', to_field='nid', related_name='users')
    follower = models.ForeignKey(verbose_name='粉絲', to='UserInfo', to_field='nid', related_name='followers')
 
class Blog(models.Model):
 
    """
    博客信息
    """
    nid = models.BigAutoField(primary_key=True)
    title = models.CharField(verbose_name='個人博客標題', max_length=64)
    site = models.CharField(verbose_name='個人博客后綴', max_length=32, unique=True)
    theme = models.CharField(verbose_name='博客主題', max_length=32)
    user = models.OneToOneField(to='UserInfo', to_field='nid')
    def __str__(self):
        return self.title
 
class Category(models.Model):
    """
    博主個人文章分類表
    """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='分類標題', max_length=32)
 
    blog = models.ForeignKey(verbose_name='所屬博客', to='Blog', to_field='nid')
 
class Article(models.Model):
 
    nid = models.BigAutoField(primary_key=True)
    title = models.CharField(max_length=50, verbose_name='文章標題')
    desc = models.CharField(max_length=255, verbose_name='文章描述')
    read_count = models.IntegerField(default=0)
    comment_count= models.IntegerField(default=0)
    up_count = models.IntegerField(default=0)
    down_count = models.IntegerField(default=0)
    category = models.ForeignKey(verbose_name='文章類型', to='Category', to_field='nid', null=True)
    create_time = models.DateField(verbose_name='創建時間')
    blog = models.ForeignKey(verbose_name='所屬博客', to='Blog', to_field='nid')
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
)
 
 
class ArticleDetail(models.Model):
    """
    文章詳細表
    """
    nid = models.AutoField(primary_key=True)
    content = models.TextField(verbose_name='文章內容', )
 
    article = models.OneToOneField(verbose_name='所屬文章', to='Article', to_field='nid')
 
 
class Comment(models.Model):
    """
    評論表
    """
    nid = models.BigAutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='評論文章', to='Article', to_field='nid')
    content = models.CharField(verbose_name='評論內容', max_length=255)
    create_time = models.DateTimeField(verbose_name='創建時間', auto_now_add=True)
 
    parent_comment = models.ForeignKey('self', blank=True, null=True, verbose_name='父級評論')
    user = models.ForeignKey(verbose_name='評論者', to='UserInfo', to_field='nid')
 
    up_count = models.IntegerField(default=0)
 
    def __str__(self):
        return self.content
 
class ArticleUpDown(models.Model):
    """
    點贊表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True)
    article = models.ForeignKey("Article", null=True)
    models.BooleanField(verbose_name='是否贊')
 
class CommentUp(models.Model):
    """
    點贊表
    """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True)
    comment = models.ForeignKey("Comment", null=True)
 
 
class Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='標簽名稱', max_length=32)
    blog = models.ForeignKey(verbose_name='所屬博客', to='Blog', to_field='nid')
 
 
 
class Article2Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid')
    tag = models.ForeignKey(verbose_name='標簽', to="Tag", to_field='nid')
View Code

1.select_related


1.1簡單使用:

對於一對一字段(OneToOneField)和外鍵字段(ForeignKey),可以使用select_related 來對QuerySet進行優化。

select_related 返回一個QuerySet,當執行它的查詢時它沿着外鍵關系查詢關聯的對象的數據。它會生成一個復雜的查詢並引起性能的損耗,但是在以后使用外鍵關系時將不需要數據庫查詢。

簡單說,在對QuerySet使用select_related()函數后,Django會獲取相應外鍵對應的對象,從而在之后需要的時候不必再查詢數據庫了。

下面的例子解釋了普通查詢和select_related() 查詢的區別。

查詢id=2的文章的分類名稱,下面是一個標准的查詢:

#
Hits the database. article=models.Article.objects.get(nid=2) # Hits the database again to get the related Blog object. print(article.category.title) ''' SELECT "blog_article"."nid", "blog_article"."title", "blog_article"."desc", "blog_article"."read_count", "blog_article"."comment_count", "blog_article"."up_count", "blog_article"."down_count", "blog_article"."category_id", "blog_article"."create_time", "blog_article"."blog_id", "blog_article"."article_type_id" FROM "blog_article" WHERE "blog_article"."nid" = 2; args=(2,) SELECT "blog_category"."nid", "blog_category"."title", "blog_category"."blog_id" FROM "blog_category" WHERE "blog_category"."nid" = 4; args=(4,) '''
撞了兩次庫

 如果我們使用select_related()函數:

articleList=models.Article.objects.select_related("category").all()
 
 
    for article_obj in articleList:
        #  Doesn't hit the database, because article_obj.category
        #  has been prepopulated in the previous query.
        print(article_obj.category.title)

SELECT
     "blog_article"."nid", 
     "blog_article"."title", 
     "blog_article"."desc",
     "blog_article"."read_count", 
     "blog_article"."comment_count", 
     "blog_article"."up_count", 
     "blog_article"."down_count",
     "blog_article"."category_id", 
     "blog_article"."create_time", 
     "blog_article"."blog_id", 
     "blog_article"."article_type_id", 
 
     "blog_category"."nid", 
     "blog_category"."title", 
     "blog_category"."blog_id"
 
FROM "blog_article"
LEFT OUTER JOIN "blog_category" ON ("blog_article"."category_id" = "blog_category"."nid");
只撞了一次庫

1.2多外鍵查詢:

這是針對category的外鍵查詢,如果是另外一個外鍵呢?讓我們一起看下:

article=models.Article.objects.select_related("category").get(nid=1)
print(article.articledetail)

觀察logging結果,發現依然需要查詢兩次,所以需要改為:

article=models.Article.objects.select_related("category","articledetail").get(nid=1)
print(article.articledetail)

 或者:

article=models.Article.objects
             .select_related("category")
             .select_related("articledetail")
             .get(nid=1)            # django 1.7 支持鏈式操作
print(article.articledetail)
SELECT
 
    "blog_article"."nid", 
    "blog_article"."title", 
    ......
 
    "blog_category"."nid", 
    "blog_category"."title",
    "blog_category"."blog_id", 
 
    "blog_articledetail"."nid",
    "blog_articledetail"."content",
    "blog_articledetail"."article_id"
 
   FROM "blog_article"
   LEFT OUTER JOIN "blog_category" ON ("blog_article"."category_id" = "blog_category"."nid")
   LEFT OUTER JOIN "blog_articledetail" ON ("blog_article"."nid" = "blog_articledetail"."article_id") 
   WHERE "blog_article"."nid" = 1; args=(1,)

只撞了一次庫

1.3深層查詢

# 查詢id=1的文章的用戶姓名
 
    article=models.Article.objects.select_related("blog").get(nid=1)
    print(article.blog.user.username)

依然需要查詢兩次:

SELECT
    "blog_article"."nid", 
    "blog_article"."title", 
    ......
 
     "blog_blog"."nid", 
     "blog_blog"."title", 
 
   FROM "blog_article" INNER JOIN "blog_blog" ON ("blog_article"."blog_id" = "blog_blog"."nid") 
   WHERE "blog_article"."nid" = 1; 
 
 
 
 
SELECT
    "blog_userinfo"."password", 
    "blog_userinfo"."last_login",
    ......
 
FROM "blog_userinfo"
WHERE "blog_userinfo"."nid" = 1; 

這是因為第一次查詢沒有query到userInfo表,所以,修改如下:

article=models.Article.objects.select_related("blog__user").get(nid=1)
print(article.blog.user.username)

SELECT
 
"blog_article"."nid", "blog_article"."title",
......
 
 "blog_blog"."nid", "blog_blog"."title", 
......
 
 "blog_userinfo"."password", "blog_userinfo"."last_login",
......
 
FROM "blog_article"
 
INNER JOIN "blog_blog" ON ("blog_article"."blog_id" = "blog_blog"."nid") 
 
INNER JOIN "blog_userinfo" ON ("blog_blog"."user_id" = "blog_userinfo"."nid") 
WHERE "blog_article"."nid" = 1; 

1.4總結

1.select_related主要針一對一和多對一關系進行優化。
2.select_related使用SQL的JOIN語句進行優化,通過減少SQL查詢的次數來進行優化、提高性能。
3.可以通過可變長參數指定需要select_related的字段名。也可以通過使用雙下划線“__”連接字段名來實現指定的遞歸查詢。
4.沒有指定的字段不會緩存,沒有指定的深度不會緩存,如果要訪問的話Django會再次進行SQL查詢。
5.也可以通過depth參數指定遞歸的深度,Django會自動緩存指定深度內所有的字段。如果要訪問指定深度外的字段,Django會再次進行SQL查詢。
6.也接受無參數的調用,Django會盡可能深的遞歸查詢所有的字段。但注意有Django遞歸的限制和性能的浪費。
7.Django >= 1.7,鏈式調用的select_related相當於使用可變長參數。Django < 1.7,鏈式調用會導致前邊的select_related失效,只保留最后一個。

2.prefetch_related()

對於多對多字段(ManyToManyField)和一對多字段,可以使用prefetch_related()來進行優化。

prefetch_related()的解決方法是,分別查詢每個表,然后用Python處理他們之間的關系。

# 查詢所有文章關聯的所有標簽
    article_obj=models.Article.objects.all()
    for i in article_obj:
 
        print(i.tags.all())  #4篇文章: hits database 5

改為prefetch_related:

# 查詢所有文章關聯的所有標簽
    article_obj=models.Article.objects.prefetch_related("tags").all()
    for i in article_obj:
 
        print(i.tags.all())  #4篇文章: hits database 2


SELECT "blog_article"."nid",
               "blog_article"."title", 
               ......
 
FROM "blog_article";
 
 
 
SELECT
  ("blog_article2tag"."article_id") AS "_prefetch_related_val_article_id", 
  "blog_tag"."nid", 
  "blog_tag"."title",
  "blog_tag"."blog_id"
   FROM "blog_tag"
  INNER JOIN "blog_article2tag" ON ("blog_tag"."nid" = "blog_article2tag"."tag_id") 
  WHERE "blog_article2tag"."article_id" IN (1, 2, 3, 4); 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM