限制*大* Django QuerySet中的内存使用
我有一个任务,需要定期在我的数据库中的“绝大多数”对象上运行(比如每天一次,每周一次,随便)。基本上,这意味着我有一个查询,它在自己的线程中运行,像这样。
for model_instance in SomeModel.objects.all():
do_something(model_instance)
(注意,这实际上是一个filter()而不是all(),但无论如何,我最终还是选择了一个非常大的对象集合。)
我遇到的问题是,运行一段时间后,我的托管服务提供商会杀掉这个线程,因为我使用的内存太多。我猜测,所有这些内存使用都是因为虽然我查询返回的QuerySet
对象最开始占用的内存很小,但随着我遍历这些对象,QuerySet
对象会缓存每一个model_instance
,导致内存不断增加。
我的问题是,“有什么好的方法可以以节省内存的方式遍历几乎所有的SomeModel
对象?”或者也许我的问题是“如何从django的查询集中‘取消缓存’模型实例?”
编辑:我实际上是使用查询集的结果来构建一系列新对象。因此,我根本没有更新被查询的对象。
8 个回答
你不能使用 Model.objects.all().iterator()
,因为这样会一次性把表里的所有数据都取出来。你也不能用 Model.objects.all()[offset:offset+pagesize]
,因为这样会把结果缓存起来。这两种方法都会超过你的内存限制。
我尝试把这两种方法结合起来,结果成功了:
offset = 0
pagesize = 1000
count = Model.objects.all().count()
while offset < count:
for m in Model.objects.all()[offset : offset + pagesize].iterator:
do_something with m
offset += pagesize
你可以根据自己的需求调整 pagesize
,如果需要的话,还可以把 [offset : offset + pagesize]
改成 [offset * pagesize : (offset + 1) * pagesize]
,这样可能更适合你。当然,别忘了把 Model
替换成你实际使用的模型名称。
那使用Django核心的分页器和页面对象怎么样呢?这些在这里有详细的说明:
https://docs.djangoproject.com/en/dev/topics/pagination/
可以像这样做:
from django.core.paginator import Paginator
from djangoapp.models import SomeModel
paginator = Paginator(SomeModel.objects.all(), 1000) # chunks of 1000
for page_idx in range(1, paginator.num_pages):
for row in paginator.page(page_idx).object_list:
# here you can do what you want with the row
print "done processing page %s" % page_idx
我最后做的事情是创建了一个可以“包装”查询集(QuerySet)的东西。它的工作原理是先对查询集进行深拷贝,然后使用切片语法,比如说 some_queryset[15:45]
,但是在这个切片完全遍历完之后,它会再对原始查询集进行一次深拷贝。这意味着在内存中只存储了“这个”特定切片返回的对象。
class MemorySavingQuerysetIterator(object):
def __init__(self,queryset,max_obj_num=1000):
self._base_queryset = queryset
self._generator = self._setup()
self.max_obj_num = max_obj_num
def _setup(self):
for i in xrange(0,self._base_queryset.count(),self.max_obj_num):
# By making a copy of of the queryset and using that to actually access
# the objects we ensure that there are only `max_obj_num` objects in
# memory at any given time
smaller_queryset = copy.deepcopy(self._base_queryset)[i:i+self.max_obj_num]
logger.debug('Grabbing next %s objects from DB' % self.max_obj_num)
for obj in smaller_queryset.iterator():
yield obj
def __iter__(self):
return self
def next(self):
return self._generator.next()
所以不是...
for obj in SomeObject.objects.filter(foo='bar'): <-- Something that returns *a lot* of Objects
do_something(obj);
你可以这样做...
for obj in MemorySavingQuerysetIterator(in SomeObject.objects.filter(foo='bar')):
do_something(obj);
请注意,这样做的目的是为了在你的 Python 解释器 中 节省内存。它的基本原理是通过进行 更多 的数据库查询来实现的。通常,人们的目标是尽量减少数据库查询次数,而不太考虑内存使用。不过希望有人会觉得这个方法有用。