有问题的表包含大约一千万行。
for event in Event.objects.all():
print event
这会导致内存使用量稳定增加到4 GB左右,此时行会快速打印。第一行打印之前的漫长延迟令我感到惊讶 - 我预计它会立即打印出来。
我也试过Event.objects.iterator()
,行为方式相同。
我不明白Django加载到内存中的原因或为什么会这样做。我期望Django在数据库级别迭代结果,这意味着结果将以大致恒定的速率打印(而不是在漫长的等待之后立即打印)。
我误解了什么?
(我不知道它是否相关,但我正在使用PostgreSQL。)
答案 0 :(得分:94)
Nate C很接近,但并不完全。
来自the docs:
您可以通过以下方式评估QuerySet:
迭代。 QuerySet是可迭代的,并且在您第一次迭代它时执行其数据库查询。例如,这将打印数据库中所有条目的标题:
for e in Entry.objects.all(): print e.headline
因此,当您第一次进入该循环并获取查询集的迭代形式时,将同时检索您的一千万行。您遇到的等待是Django加载数据库行并为每个行创建对象,然后返回您可以实际迭代的内容。然后你就拥有了记忆中的一切,结果就会消失。
从我对文档的阅读中,iterator()
只会绕过QuerySet的内部缓存机制。我认为它可能是一个一个接一个的事情,但相反,你的数据库需要1000万次点击。也许不是那么可取。
有效地迭代大型数据集是我们仍然没有做到的事情,但是有一些片段可能会对您的目的有用:
答案 1 :(得分:34)
可能不是更快或更有效,但作为现成的解决方案,为什么不使用django core的Paginator和Page对象记录在这里:
https://docs.djangoproject.com/en/dev/topics/pagination/
这样的事情:
from django.core.paginator import Paginator
from djangoapp.models import model
paginator = Paginator(model.objects.all(), 1000) # chunks of 1000, you can
# change this to desired chunk size
for page in range(1, paginator.num_pages + 1):
for row in paginator.page(page).object_list:
# here you can do whatever you want with the row
print "done processing page %s" % page
答案 2 :(得分:21)
Django的默认行为是在评估查询时缓存QuerySet的整个结果。您可以使用QuerySet的迭代器方法来避免此缓存:
for event in Event.objects.all().iterator():
print event
https://docs.djangoproject.com/en/dev/ref/models/querysets/#iterator
iterator()方法计算查询集,然后直接读取结果,而不在QuerySet级别进行缓存。当迭代您只需要访问一次的大量对象时,此方法可以获得更好的性能并显着减少内存。请注意,缓存仍在数据库级别完成。
使用iterator()减少了我的内存使用量,但它仍然高于我的预期。使用mpaf建议的分页器方法使用的内存要少得多,但对于我的测试用例来说要慢2-3倍。
from django.core.paginator import Paginator
def chunked_iterator(queryset, chunk_size=10000):
paginator = Paginator(queryset, chunk_size)
for page in range(1, paginator.num_pages + 1):
for obj in paginator.page(page).object_list:
yield obj
for event in chunked_iterator(Event.objects.all()):
print event
答案 3 :(得分:7)
这是来自文档: http://docs.djangoproject.com/en/dev/ref/models/querysets/
在您执行评估查询集的操作之前,实际上不会发生数据库活动。
因此,当print event
运行时,查询将触发(根据您的命令进行全表扫描)并加载结果。你要求所有的对象,没有办法得到第一个对象而没有获得所有对象。
但如果你这样做:
Event.objects.all()[300:900]
http://docs.djangoproject.com/en/dev/topics/db/queries/#limiting-querysets
然后它会在内部为sql添加偏移和限制。
答案 4 :(得分:6)
对于大量记录,database cursor表现更好。你需要在Django中使用原始SQL,Django-cursor与SQL cursur不同。
Nate C建议的LIMIT - OFFSET方法可能足以满足您的需求。对于大量数据,它比光标慢,因为它必须反复运行相同的查询,并且必须跳过越来越多的结果。
答案 5 :(得分:6)
Django没有很好的解决方案来从数据库中获取大型项目。
import gc
# Get the events in reverse order
eids = Event.objects.order_by("-id").values_list("id", flat=True)
for index, eid in enumerate(eids):
event = Event.object.get(id=eid)
# do necessary work with event
if index % 100 == 0:
gc.collect()
print("completed 100 items")
values_list可用于获取数据库中的所有ID,然后单独获取每个对象。有一段时间,大型对象将在内存中创建,并且不会被垃圾收集直到退出循环。上面的代码在每消耗100个项目后进行手动垃圾收集。
答案 6 :(得分:4)
因为这样整个查询集的对象一次性加载到内存中。您需要将查询集分成较小的可消化位。这样做的模式称为勺子喂食。这是一个简短的实现。
def spoonfeed(qs, func, chunk=1000, start=0):
''' Chunk up a large queryset and run func on each item.
Works with automatic primary key fields.
chunk -- how many objects to take on at once
start -- PK to start from
>>> spoonfeed(Spam.objects.all(), nom_nom)
'''
while start < qs.order_by('pk').last().pk:
for o in qs.filter(pk__gt=start, pk__lte=start+chunk):
yeild func(o)
start += chunk
要使用此功能,您需要编写一个对对象执行操作的函数:
def set_population_density(town):
town.population_density = calculate_population_density(...)
town.save()
并在您的查询集上运行该函数:
spoonfeed(Town.objects.all(), set_population_density)
这可以通过多处理进一步改进,以并行地对多个对象执行func
。
答案 7 :(得分:2)
这里包含len和count的解决方案:
class GeneratorWithLen(object):
"""
Generator that includes len and count for given queryset
"""
def __init__(self, generator, length):
self.generator = generator
self.length = length
def __len__(self):
return self.length
def __iter__(self):
return self.generator
def __getitem__(self, item):
return self.generator.__getitem__(item)
def next(self):
return next(self.generator)
def count(self):
return self.__len__()
def batch(queryset, batch_size=1024):
"""
returns a generator that does not cache results on the QuerySet
Aimed to use with expected HUGE/ENORMOUS data sets, no caching, no memory used more than batch_size
:param batch_size: Size for the maximum chunk of data in memory
:return: generator
"""
total = queryset.count()
def batch_qs(_qs, _batch_size=batch_size):
"""
Returns a (start, end, total, queryset) tuple for each batch in the given
queryset.
"""
for start in range(0, total, _batch_size):
end = min(start + _batch_size, total)
yield (start, end, total, _qs[start:end])
def generate_items():
queryset.order_by() # Clearing... ordering by id if PK autoincremental
for start, end, total, qs in batch_qs(queryset):
for item in qs:
yield item
return GeneratorWithLen(generate_items(), total)
用法:
events = batch(Event.objects.all())
len(events) == events.count()
for event in events:
# Do something with the Event
答案 8 :(得分:0)
我通常使用原始的MySQL原始查询代替Django ORM进行此类任务。
MySQL支持流模式,因此我们可以安全快速地遍历所有记录,而不会出现内存错误。
import MySQLdb
db_config = {} # config your db here
connection = MySQLdb.connect(
host=db_config['HOST'], user=db_config['USER'],
port=int(db_config['PORT']), passwd=db_config['PASSWORD'], db=db_config['NAME'])
cursor = MySQLdb.cursors.SSCursor(connection) # SSCursor for streaming mode
cursor.execute("SELECT * FROM event")
while True:
record = cursor.fetchone()
if record is None:
break
# Do something with record here
cursor.close()
connection.close()
价: