在数据变化的同时遍历大型Django查询集
遍历一个查询集,比如这样:
class Book(models.Model):
# <snip some other stuff>
activity = models.PositiveIntegerField(default=0)
views = models.PositiveIntegerField(default=0)
def calculate_statistics():
self.activity = book.views * 4
book.save()
def cron_job_calculate_all_book_statistics():
for book in Book.objects.all():
book.calculate_statistics()
...是没问题的。不过,这个是一个定时任务。在这个过程中,book.views
的值会不断增加。如果在这个定时任务运行的时候,book.views
被修改了,它的值就会被还原。
现在,book.views
并不是被这个定时任务修改的,但在调用 .all()
查询集的时候,它的值被缓存了。当我执行 book.save()
的时候,我感觉它使用的是旧的 book.views
值。
有没有办法确保只更新 activity
字段?另外,假设有10万个书籍,这样运行会花费相当长的时间。但是 book.views
的值会是查询集最开始运行时的值。解决办法是直接使用 .iterator()
吗?
更新:这就是我实际在做的。如果你有关于如何让这个过程更顺利的想法,我非常欢迎。
def calculate_statistics(self):
self.activity = self.views + self.hearts.count() * 2
# Can't do self.comments.count with a comments GenericRelation, because Comment uses
# a TextField for object_pk, and that breaks the whole system. Lame.
self.activity += Comment.objects.for_model(self).count() * 4
self.save()
3 个回答
1
无论你怎么解决这个问题,都要注意与事务相关的问题。例如,默认的事务隔离级别在MySQL数据库中是设置为“可重复读”。再加上Django和数据库后端在进行事务时会以特定的自动提交模式工作,这意味着即使你使用了(非常不错的)whrde的建议,`views`的值可能就不再有效了。我可能说错了,但还是提醒你要小心。
3
除了其他人提到的,如果你在处理一个很大的查询结果时,应该使用 iterator() 方法:
Book.objects.filter(stuff).order_by(stuff).iterator()
这样做会让 Django 在遍历这些数据时不进行缓存(因为缓存大量数据会占用很多内存)。
4
下面的代码可以在Django 1.1中帮你完成这个任务,不需要使用循环:
from django.db.models import F
Book.objects.all().update(activity=F('views')*4)
你也可以进行更复杂的计算:
for book in Book.objects.all().iterator():
Book.objects.filter(pk=book.pk).update(activity=book.calculate_activity())
这两种方法都有可能导致活动字段和其他部分不同步,但我想你对此应该没问题,因为你是在定时任务中计算它。