Simple Life

和这个世界交手这许多年   你是否光彩依旧,兴致盎然...

您现在的位置是:首页 爱折腾 爱折腾详情

Django的数据查询优化

发布时间:2015-7-02 作者:Felix 浏览(1387)

        Django的数据查询优化是个很重要的问题,稍不留意,你的接口可能就会耗费很长时间。尤其逻辑比较复杂的,那就更需要你细致的优化下。虽然对一个项目,优化工作都是放在后期来做的,但是如果开始就注重些技巧,对后期优化是个不小的帮助,减少了工作量,何乐而不为呢。

        要做优化,最好就是有个工具帮助你调试,Django_Debug_Toolbar算是一个比较好的工具,可以看下我之前的 Django开发利器Django Debug Toolbar安装http://felixonly.com/blog/detail/16/)可以帮助你分析性能,调试、优化sql查询等。

        这里整理了些平时遇到的还有网上搜罗到注意点,以后会继续更新。

 

一 利用标准数据库优化技术:

        传统数据库优化技术博大精深,不同的数据库有不同的优化技巧,但重心还是有规则的。在这里算是题外话,挑两点通用的说说:

        (1)索引,给关键的字段添加索引,性能能更上一层楼,如给表的关联字段,搜索频率高的字段加上索引等。Django建立实体的时候,支持给字段添加索引,具体参考Django.db.models.Field.db_index。按照经验,Django建立实体之前应该早想好表的结构,尽量想到后面的扩展性,避免后面的表的结构变得面目全非。

title = models.CharField(max_length=100, blank=True, db_index=True, verbose_name=u'标题')

        (2)使用适当字段类型,本来varchar就搞定的字段,就别要text类型,小细节别不关紧要,后头数据量一上去,几亿几亿的数据,小字段很可能是大问题。

       

二 了解Django的QuerySets:

        了解Django的QuerySets对象,对优化简单程序有至关重要的作用。QuerySets是有缓存的,一旦取出来,它就会在内存里呆上一段时间,尽量重用它。举个简单的例子:


        了解缓存属性:

>>> entry = Entry.objects.get(id=1)
>>> entry.blog   # 博客实体第一次取出,是要访问数据库的
>>> entry.blog   # 第二次再用,那它就是缓存里的实体了,不再访问数据库

        但下面的例子就不一样,

>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all()   # 第一次all函数会查询数据库
>>> entry.authors.all()   # 第二次all函数还会查询数据库

        all,count,exists是调用函数(需要连接数据库处理结果的)

注意:1. 在模板template里的代码,模板里不允许括号,但如果使用此类的调用函数,一样去连接数据库的,

                  能用缓存的数据就别连接到数据库去处理结果。

              2. 自定义的实体属性,如果调用函数的,记得自己加上缓存策略


        利用好模板的with标签:

        模板中多次使用的变量,要用with标签,把它看成变量的缓存行为吧。


        使用QuerySets的iterator():

        通常QuerySets先调用iterator再缓存起来,当获取大量的实体列表而仅使用一次时,缓存行为会耗费宝贵的内存,这时iterator()能帮到你,iterator()只调用iterator而省去了缓存步骤,显著减少内存占用率。

class TestPage(View):
    def get(self, request):
        t = Test.objects.filter()
        for s in t:
            print s.name, s.content
        print t[0].name
        return render(request, 'test.html')

iteraotr.png

class TestPage(View):

    def get(self, request):
        t = Test.objects.filter()
        for s in t.iterator():  # 利用iterator
            print s.name, s.content
        print t[0].name  # 这里会再次查询,因为没有缓存
        return render(request, 'test.html')

iter2.png


三 数据库的工作就交给数据库本身计算,别用Python处理:

        1. 使用 filter and exclude 过滤不需要的记录,这两个是最常用语句,相当是SQL的where。

        2. 同一实体里使用F()表达式过滤其他字段。

        3. 使用annotate对数据库做聚合运算。

        不要用python语言对以上类型数据过滤筛选,同样的结果,python处理复杂度要高,而且效率不高, 白白浪费内存。


        使用QuerySet.extra():

        extra虽然扩展性不太好,但功能很强大,如果实体里需要需要增加额外属性,不得已时,通过extra来实现,也是个好办法。


        使用原生的SQL语句:

        Django 执行原始sql (http://felixonly.com/blog/detail/2/)

        如果发现Django的ORM已经实现不了你的需求,而extra也无济于事的时候,那就用原生SQL语句吧,用Djangoango.db.connection.queries去实现你需要的东西。


四 如果需要就一次性取出你所需要的数据:

        1. 单一动作(如:同一个页面)需要多次连接数据库时,最好一次性取出所有需要的数据,减少连接数据库次数。此类需求推荐使用QuerySet.select_related() 和 prefetch_related()。

        2. 相反,别取出你不需要的东西,模版templates里往往只需要实体的某几个字段而不是全部,这时QuerySet.values() 和 values_list(),对你有用,它们只取你需要的字段,返回字典dict和列表list类型的东西,在模版里够用即可,这可减少内存损耗,提高性能。In [1]: from temporary.models import Test

In [2]: t = Test.objects.filter()
In [3]: t.values('name', 'content')
Out[3]: [{'content': u'Find what repositories the GitHub community', 'name': u'hello'}, 
        {'content': u'Trending repositories', 'name': u'welcome'}]
In [4]: t.values_list('name', flat=True)
Out[4]: [u'hello', u'welcome']

        3. 同样QuerySet.defer()和only()对提高性能也有很大的帮助,一个实体里可能有不少的字段,有些字段包含很多元数据,比如博客的正文,很多字符组成,Django获取实体时(取出实体过程中会进行一些python类型转换工作),我们可以延迟大量元数据字段的处理,只处理需要的关键字段,这时QuerySet.defer()就派上用场了,在函数里传入需要延时处理的字段即可;而only()和defer()是相反功能。

t = Test.objects.defer('content').get(id=1)
print t.name  # 不需要查询
print t.content  # 再次查询

t = Test.objects.only('name').get(id=1)
print t.name  # 不需要查询
print t.content  # 再次查询

        4. 使用QuerySet.count()代替len(queryset),虽然这两个处理得出的结果是一样的,但前者性能优秀很多。同理判断记录存在时,QuerySet.exists()比if queryset实在强得太多了。

t = Test.objects.filter()
print t[0].name
print t.count()   # 耗时89.72毫秒

t = Test.objects.filter()
print t[0].name
print len(t)  # 耗时92.98毫秒

        当然一样的结果,在缓存里已经存在,就别滥用count(),exists(),all()函数了。


五 懂减少数据库的连接数:

        1. 使用 QuerySet.update() 和 delete(),这两个函数是能批处理多条记录的,适当使用它们事半功倍;如果可以,别一条条数据去update delete处理,批量操作不会调用类中定义的 save() 或 delete() 方法。

        2. 对于一次性取出来的关联记录,获取外键的时候,直接取关联表的属性,而不是取关联属性,如:entry.blog.id    优于    entry.blog_id

        3. 多对多的关系,


my_band.members.add(me, my_friend)    
优于
my_band.members.add(me)
my_band.members.add(my_friend)



基于 Django 搭建

服务器采用的 阿里云

域名来自 万网

苏ICP备16015443号

©2015-2016 felixglow.com.

GitHub

Design by Felix