在数据变化的同时遍历大型Django查询集

5 投票
3 回答
2530 浏览
提问于 2025-04-15 18:16

遍历一个查询集,比如这样:

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())

这两种方法都有可能导致活动字段和其他部分不同步,但我想你对此应该没问题,因为你是在定时任务中计算它。

撰写回答