Django的queryset的特性及使用优化(带实例)


简言: Django的QuerySet具有延迟特性,仅在强制操作下才会被执行,这种行为使得QuerySet更加的高效
我们可以连接任意一个过滤器到一个QuerySet上,在QuerySet计算之前不会访问数据库

1. QuerySet被计算的场合

  • 首次迭代时
  • 当对QuerySet访问时,如Post.objects.all()[:4]
  • 当对QuerySet缓存时
  • 当在QuerySet上调用repr()或len()时
  • 当在QuerySet上显示调用list()时
  • 当在某个语句中对QuerySet进行测试时,如bool(), or, and 或 if

2. queryset使用实战(以Blog为例)

我们测试一下下面这些语句执行的sql,需要在settings里加入 LOGGING(验证方式)

1. 我们使用`posts = Blog.objects.all() 或者 posts = Blog.objects.filter()

  • 上面的代码并没有运行任何的数据库查询。你可以使用posts,给它加上一些过滤条件,或者将它传给某个函数,这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一

2. queryset是具有cache的

  • 当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行
    (evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset,
    你不需要重复运行通用的查询。我们使用两次for循环,可以看到logging只会打印一次

      posts = Post.published.all()
      for post in posts:  # 执行sql查询
      	print(post)
      	print(post.publish)
     	 	print(post.title)
    
      for post in posts:  # 不执行sql查询,使用cache
      	print(post)
    

3. 简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,为了避免这个,可以用exists()方法来检查是否有数据:

posts = Post.published.all()
if posts.exists():  # .exists()判断值是否存在,可以避免数据放入queryset的cache
    for post in posts:
        print(post)
if posts:  # if判断是否存在也会进行数据库查询并将返回结果放入QuerySet的cache
	print(2333)

关于是否使用exists()的谏言:

当只是用来判断某个QuerySet是否存在的时候,使用if QuerySet.exists(),当用来判断某个QuerySet是否存在,并且后面的语句还会用到QuerySet中的数据的时候使用if QuerySet:,使用.exists反而会造成额外的开销

4. 当queryset非常巨大时,cache会成为问题

处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统
进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法
来获取数据,处理完数据就将其丢弃。

posts = Post.published.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for post in posts:
    print(post)
    print(post.publish)
# 但是,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没有可以遍历得了
for post in posts:
    print(post)

使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使
用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询

posts = Post.published.all()
for post in posts.iterator():  # 此处会进行一次sql查询,但是并没有缓存进queryset cache
    print(post)
    print(post.publish)
for post in posts.iterator():  # 此处会再次进行一次sql查询,同样没有缓存进queryset cache
    print(post)

3. 总结

  • queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。
  • 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能会造成额外的数据库查询。

4. 开启数据库的logging

在setting中加入

LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
    'console': {
        'level': 'DEBUG',
        'class': 'logging.StreamHandler',
    },
},

'loggers': {
    'django.db.backends': {
        'handlers': ['console'],
        'propagate': True,
        'level': 'DEBUG',
    },
}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章