Django ORM 查询性能优化(转)
前段时间用django做了个拨测程序。对于大量数据需在django中处理优化展示。刚好查到一篇总结得不错的BLOG文章结合程序的处理经验分享并记录。
Count() not len()
当想统计筛选出来的数据的条数时候,不要用len(),用Django QuerySet 的count() 函数,例子:
In [1]: import time
...: from myapp.models import ContentsOfBook
...: contents = ContentsOfBook.objects.all()
...: t1 = time.time()
...: print len(contents)
...: t2 = time.time()
...: print "Used time:%s" % str(t2-t1)
...:
126919
Used time:4.06234192848
In [2]: import time
...: from jfTypein.models import ContentsOfBook
...: contents = ContentsOfBook.objects.all()
...: t1 = time.time()
...: print contents.count()
...: t2 = time.time()
...: print "Used time:%s" % str(t2-t1)
...:
126919
Used time:0.0325939655304
可以明显看出,用len()方法的耗时是4.06s ,而用count()方法只用了0.03s ,原因在于Django的lazy机制,Django再做query set的时候不会把所有的数据给select出来,他是分页筛选出来的,所以在用len()方法的时候,相当于把整个query set遍历了一遍,把所有的数据都取出来对象化,耗时的同时也耗资源,会浪费空间。
大的query set最好不要直接遍历
我们经常会遇到一种情况,就是把我们筛选出来的query set 遍历一遍,去进行一些操作,这个时候我们一般会直接写个for 循环去遍历他。
需要注意的是,假如我们筛选出来的Query很大,尤其要注意的是我们调用 Model.objects.all()的时候,如果直接用for循环去遍历这些Query,Django会把他们全部进行实例化,如果数据比较大,就会占用大量的内存。
所以推荐一种做法,先把Query的ID或者主键列出来,然后再一个个查找。
# 比如我们要遍历 content_status == 4 的数据项
contents = ContentsOfBook.objects.filter(content_status=4)
ids = contents.values_list('id')
for content_id in ids:
content = ContentsOfBook.objects.get(id=content_id)
# do something
避免多次查询
有些情况我们需要筛选某个表不同条件的数据,一般我们可能会直接去写多个查询去筛选,数据项多的话,这会严重影响我们的性能:
books = [
u'book_1',
u'book_2',
u'book_3',
]
In [9]: import time
...: from jfTypein.models import ContentsOfBook
...: t1 = time.time()
...: for book in books:
...: contents_1 = ContentsOfBook.objects.filter(book=book,exit=1)
...: contents_2 = ContentsOfBook.objects.filter(book=book,exit=2)
...: contents_3 = ContentsOfBook.objects.filter(book=book,exit=3)
...: contents_4 = ContentsOfBook.objects.filter(book=book,content_status=4
...: )
...: contents_5 = ContentsOfBook.objects.filter(book=book,content_status=5
...: )
...: print contents_1.count(),contents_2.count(),contents_3.count(),conten
...: ts_4.count(),contents_5.count()
...: t2 = time.time()
...: print "Used time:%s" % str(t2-t1)
...:
...:
18 6 65 0 1041
0 0 0 0 905
0 0 0 0 882
Used time:1.456194877625
In [10]: import time
...: from jfTypein.models import ContentsOfBook
...: t1 = time.time()
...: for book in books:
...: contents = ContentsOfBook.objects.filter(book=book).values_list('
...: exit','content_status')
...: contents_1 = filter(lambda x:True if x[0]==1 else False,contents)
...: contents_2 = filter(lambda x:True if x[0]==2 else False,contents)
...: contents_3 = filter(lambda x:True if x[0]==3 else False,contents)
...: contents_4 = filter(lambda x:True if x[1]==4 else False,contents)
...: contents_5 = filter(lambda x:True if x[1]==5 else False,contents)
...: print len(contents_1),len(contents_2),len(contents_3),len(contents_4
...: ),len(contents_5)
...: t2 = time.time()
...: print "Used time:%s" % str(t2-t1)
...:
...:
18 6 65 0 1041
0 0 0 0 905
0 0 0 0 882
Used time:0.311456871033
从上面的结果可以看到,如果进行多次的orm查询,效率比一次查询的要慢上好几倍,第二种方法中把需要的字段都一次select了出来放在内存中,然后调用python的filter函数去进行筛选,避免了多次对数据库进行连接,减少了IO。
所以我们在做不同条件的查询的时候,尽量一次把数据都查询出来,当然要避免把比较大的字段也select出来,不然会占用内存,同时也会影响到性能。
尽可能批量创建
在业务中,经常会碰到需要往数据库中插入一批数据的情况,最简单的就是写个简单的for循环create了,如下:
for i in range(100):
ContentsOfBook.objects.create(
book_id = 2333 + i,
...
)
这样子插入数据性能比较低,因为这样子是执行了100条insert sql,这么频繁的IO,肯定会比较慢。Django提供了bulk_create方法,可以批量插入数据到DB中,如下:
need_create_objs = []
for i in range(100):
need_create_objs.append(
ContentsOfBook(
book_id=2333 + i
....
)
)
ContentsOfBook.objects.bulk_create(need_create_objs)
因为Django的autocommit机制,Model每次对DB的操作都会新建一个链接并且提交过去,如果代码中get, save, create这样的方法大量使用,会影响业务的性能。下面讨论下如何提高对数据进行更新的性能:
首先假设我们有一个Model(GobalSchool),定义如下:
class GobalSchool(models.Model):
id = models.AutoField(primary_key=True)
school_name = models.CharField(max_length=128)
表里有里面有4k条数据,我们需要把所有的school的名字(school_name)进行更改,为了避免不必要的OOM,我们每次实验先把所有的对象id提取出来,后面的几种方法都调用这个函数
def get_objects_id(self):
ids = GobalSchool.objects.all().values_list('id')
ids = list(map(lambda x:x[0], ids))
ids.sort()
return ids
下面是第一种写法,也是最初级性能最差的一种写法:
def run(self):
t1 = time.time()
ids = self.get_objects_id()
for _id in ids:
print(_id)
school = GobalSchool.objects.get(id=_id)
school.school_name = "my_school"
school.save()
print("use2:%s" % (time.time() - t1))
最后运行时间是:147(s),主要时间浪费在,每次get和save,在这两个函数里面,Django都是直接commit过去给SQL去执行的。
那么,减少Django对数据库的链接操作,就可以提高性能了
第二种做法是,节省大部分的get操作时间,不再是每一条条数据地取出来,每次批量取出一批数据进行操作
from functional import seq
def multi_update(self, ids):
schools = GobalSchool.objects.filter(id__in=ids)
for school in schools:
school.school_name = "my_school_save"
school.save()
def run(self):
t1 = time.time()
container_size = 1000
ids = self.get_objects_id()
bulk_ids = seq(ids).grouped(container_size) # 这里是把id分成长度为1000的若干份
bulk_ids = list(map(list, bulk_ids))
for bulk_id in bulk_ids:
self.multi_update(bulk_id)
print("use1:%s" % (time.time() - t1))
这次的运行时间是:73(s),可以看到时间节省了一半,通过省去大量的get操作,来提高了性能。
第三种做法是,我们进行数据操作的时候,我们建立一个长链接,去掉所有的save和get操作
from functional import seq
def multi_update(self, ids):
cursor = connection.cursor()
for _id in ids:
sql = "update gobal_schools set school_name = 'my_school_quick' where id = %s" % _id
cursor.execute(sql)
def run(self):
t1 = time.time()
container_size = 1000
ids = self.get_objects_id()
bulk_ids = seq(ids).grouped(container_size)
bulk_ids = list(map(list, bulk_ids))
for bulk_id in bulk_ids:
self.multi_update(bulk_id)
print("use1:%s" % (time.time() - t1))
最后我们得到的时间是:2.38(s),性能大大提高了!
Django黑魔法-transaction
为了解决频繁提交事务(commit)带来的IO消耗,Django提供了一个transaction接口,用户可以调用transaction.atomic来把Django的autocommit暂时屏蔽掉,方法如下:
from functional import seq
from django.db import transaction
@transaction.atomic
def multi_update(self, ids):
schools = GobalSchool.objects.filter(id__in=ids)
for school in schools:
school.school_name = "my_school_save"
school.save()
def run(self):
t1 = time.time()
container_size = 1000
ids = self.get_objects_id()
bulk_ids = seq(ids).grouped(container_size)
bulk_ids = list(map(list, bulk_ids))
for bulk_id in bulk_ids:
self.multi_update(bulk_id)
print("use1:%s" % (time.time() - t1))
这样我们最后得到的运行时间也是:2.5(s)
您可能也对下面文章感兴趣:
There are 1 Comments to "Django ORM 查询性能优化(转)"