博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用Django annotation,提升django查询性能
阅读量:6454 次
发布时间:2019-06-23

本文共 2504 字,大约阅读时间需要 8 分钟。

annotation的中文含义是"注解"。正如这名字所暗示的,传递给annotate函数的每个参数,都会以"注解"的形式添加到model queryset返回的每一个object里面。

和annotate经常在一起使用的是aggregation函数。

举个栗子

Blog Model有一个外键entry指向Entry model。我们想计算每个blog有多少个entry:

>>> from django.db.models import Count>>> q = Blog.objects.annotate(Count('entry'))# The name of the first blog>>> q[0].name'Blogasaurus'# The number of entries on the first blog>>> q[0].entry__count42复制代码

我们一起break down上面这部分代码:

q = Blog.objects.annotate(Count('entry'))复制代码

这里使用了Count这个aggregation函数,作用是对一个指定的Blog object,计算它对应的Entry object有多少个。Blog.objects.annotate(Count('entry'))就是对每个Blog object,计算一下与之对应entry有几个。返回值是一个queryset。与

Blog.objects.all()复制代码

的区别在于,Blog.objects.annotate(Count('entry'))中的每一项,都多了一个entry__count字段,这就是我们想要的那个数据。

q[0].nameq[0].entry__count复制代码

q是一个queryset,q[0]就是获取第一个object,他里面多了一个entry__count字段。

举个反栗子

如果你不知道annotate这个东西,你肯定会想到一种"pythonic"的方法:

q = Blog.objects.all()for blog in q:    entry__count = blog.entry.count()    print(blog.name)    print(entry__count)复制代码

这种方法更容易理解,但是会杀死你的性能。假如你有10W条blog,q = Blog.objects.all() 这里进行了一次查询,for循环那里,对每一个blog都要进行一次查询,所以总查询次数是10W+1次。我们知道:django orm是对sql进行的一层封装,有封装自然就会有性能损失。每一次django的查询,都要从Python层进入数据库层,然后再从数据库层进入Python层,即使这样的一次转换时间是很短的,但是这么多次累计起来,消耗的无意义时间是很可观的。

而前面那种方法,总查询次数只有一次,从Python层进入数据库层再回到Python层的次数只有一次,效率当然要高很多!

django orm有一个性能优化技巧:尽可能减少Python层和数据库层转换的次数。而Python的for循环天然会增加这种转换次数。所以对于一些简单的逻辑,可以考虑使用annotate取代for循环。

勘误

很感谢有些朋友指出的,annotate并不一定能减少IO次数。

其实是书本(《数据库原理及应用》)第九章的问题,查询优化的问题,用了annotation和不用,看底层如何存储和存取方法是什么?文中举的实例是10w条,第二条是顺序遍历,annotation也不一定会一次都读到内存里啊,还要看预留缓冲区的大小,每个物理块存多少条数据,才能决定io次数,查询效率的高低与查询逻辑或查询语句的优略有关,但到最后还是要归结到底层。

所以用IO次数来解释性能差异是不严谨的,应该用Python层到数据库层的转换次数来解释。

下面来看一个我实际做的一个测试,看看使用annotate和使用for循环,性能差异到底有多大:

数据库中WX_User这个model一共有15W条数据。其中有一个ManyToManyField字段:

selected_stocks = models.ManyToManyField(Company, blank=True)复制代码

我们想知道每个用户有多少个selected_stocks。

方法一:annotate

def annotate_test(reuqest):    from django.db.models import Count    import time    start = time.time()    q = WX_User.objects.annotate(        stock_count=Count('selected_stocks')    )    data = []    for user in q:        data.append(user.stock_count)    end = time.time()    return JsonResponse({        'spent': end - start    })复制代码

耗时10.7 s。

方法二:使用for循环

def annotate_test2(reuqest):    import time    start = time.time()    q = WX_User.objects.all()    data = []    for user in q:        data.append(user.selected_stocks.count())    end = time.time()    return JsonResponse({        'spent': end - start    })复制代码

耗时457s。

二者的性能差距是巨大的。

打个广告

关注我的微信公众号

转载地址:http://cafzo.baihongyu.com/

你可能感兴趣的文章
poi导出word
查看>>
云终端选购注意规避五个陷阱
查看>>
关于 Java 对象序列化您不知道的 5 件事
查看>>
cronolog介绍
查看>>
rpm包管理命令使用详解
查看>>
Linux 的 NUMA 技术
查看>>
虚拟磁带库VTL在实践中的优势
查看>>
人生需要放下的八样东西
查看>>
企业级Nginx Web 服务优化实战
查看>>
IE和Firefox对同一域名进行请求的并发连接数限制
查看>>
arm linux ppp拨号gprs上网移植
查看>>
linux新建用户的全程解析
查看>>
微软原版Windows XP Pro With SP3 VOL MSDN原版镜像
查看>>
学点Unicode又不会死
查看>>
linux关于关闭防火墙和selinux的操作
查看>>
python 文件操作
查看>>
MCSE命令学习持续更新
查看>>
eclipse环境快速配置
查看>>
solaris11忘记root密码的处理方法
查看>>
Windows Server 2016 主域控制器搭建(一)
查看>>