Django:如何将博客文章的浏览次数增加1?这种方法高效吗?

30

我在我的主页视图中有以下代码。

latest_entry_list = Entry.objects.filter(is_published=True).order_by('-date_published')[:10]
for entry in latest_entry_list:
    entry.views = entry.views + 1
    entry.save()

如果从初始查询返回了十行(限制),那么保存是否会向数据库发出10个单独的更新调用,还是Django“聪明”到只发出一个更新调用?

是否有更有效的方法来实现这个结果?

5个回答

70

您可以使用F()对象来实现此操作。

以下是导入F的方法:from django.db.models import F

Django 1.1中的新功能.
调用update也可以使用F()对象来根据模型中另一个字段的值更新一个字段。这在基于当前值递增计数器时非常有用。

Entry.objects.filter(is_published=True).update(views=F('views')+1)

虽然您不能对切片的查询集进行更新...编辑:实际上您可以...

这可以完全在django ORM中完成。您需要两个SQL查询:

  1. 进行过滤并收集主键列表
  2. 对匹配任何这些主键的非切片查询集进行更新。

获取非切片查询集是困难的。我思考使用in_bulk,但它返回一个字典而不是一个查询集。通常会使用Q对象来执行复杂的OR类型查询,这将起作用,但pk__in更简单地完成了任务。

latest_entry_ids = Entry.objects.filter(is_published=True)\
                                      .order_by('-date_published')
                                      .values_list('id', flat=True)[:10]  
non_sliced_query_set = Entry.objects.filter(pk__in=latest_entry_ids)  
n = non_sliced_query_set.update(views=F('views')+1)  
print n or 0, 'items updated'

由于Django执行查询的方式是惰性的,因此无论更新多少项,结果仅需2次数据库操作。


您可以使用value list直接获取id列表:latest_entry_ids = Entry.objects.filter(is_published=True)
.order_by('-date_published')
.values_list('id', flat=True)[:10]
- Guillaume Esquevin
谢谢@Guillaume,我已经加入了。 - Tom Viner
这是如何导入 F 的方法:from django.db.models import F。 我尝试进行编辑,但被拒绝了。这个答案在谷歌上很容易找到,但从这里获取这些信息几乎是不可能的。它不在答案中,也不在链接的文档中,而且通过谷歌也很难找到(因为它只是一个字符)。 - graup
1
不要忘记将两个查询都包装在“with transaction.atomic()”块中。 - Dmitriy Sintsov
MySQL的某些版本不支持使用切片的in语句,尝试将values_list转换为一个真正的列表,像这样:latest_entry_ids = list(Entry.objects.filter(is_published=True)\ .order_by('-date_published') .values_list('id', flat=True)[:10]) - mrroot5
显示剩余3条评论

13

你可以在单个事务中处理更新,这可以显著提高性能。使用一个单独的函数,并用 @transaction.commit_manually 进行修饰。

@transaction.commit_manually
def update_latest_entries(latest_entry_list):
    for entry in latest_entry_list:
        entry.views += 1
        entry.save()
    transaction.commit()

2
我已经实现了这个,虽然我还没有注意到任何速度上的改进,但你教给了我一些新东西——所以谢谢你! - Ty.

3
如果您真的需要效率,目前您需要进入SQL并自己运行更新。然而,在这种情况下,这不值得增加复杂性。
到Django 1.1时,您将能够通过ORM使用F() objects引用更新值中的字段来一次性进行单个SQL调用。

2

相对于之前的版本,这是一项性能改进。这将导致一个数据库查询,其中包含一个子查询。

latest_entry_query_set = Entry.objects.filter(is_published=True)
                                      .order_by('-date_published')[:10]  
non_sliced_query_set = Entry.objects.filter(pk__in=latest_entry_query_set.values('id'))  
n = non_sliced_query_set.update(views=F('views')+1)  
print n or 0, 'items updated'

2

修改版

您正在更新10个独立的、不同的对象。

这10个独立的、不同的更新不能轻易地合并为一个神奇的更新,从而触及10个对象。


啊,谢谢你。我是 Django 的新手,看来我有些困惑了。你说得对,没有什么神奇的 SQL 调用可以做到我要求的事情。 - Ty.
1
嗯,实际上有一个神奇的SQL调用可以做到你所要求的:UPDATE <table> SET views = views+1 WHERE ... 不过Django的ORM目前还不能为你完成这个操作。即将推出。 - Carl Meyer
似乎我又被纠正了:(UPDATE blog_entry SET views = views + 1 WHERE id = 1 OR id = 2 OR ID = 3)。谢谢Carl。 - Ty.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接