Django:更新对象浏览量时不修改updated_at字段的实现与疑问
问题背景与疑问
定义的模型
class Announcement(models.Model): ... created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) views = models.PositiveIntegerField(default=0, editable=False)
初始视图代码
class AnnouncementDetailView(DetailView): model = Announcement context_object_name = 'announcement' template_name = 'web/index.html' def get(self, *args, **kwargs): announcement = get_object_or_404(Announcement, id=kwargs['id']) announcement.views += 1 announcement.save() return super().get(self, *args, **kwargs)
遇到的问题
- 想知道除了F表达式外,更新字段但不修改
updated_at的其他方法 - 仅希望保存
views字段变更,但调用save()会自动更新updated_at,导致页面刷新时该字段始终显示当前时间,不符合需求
修改后的视图代码
... def get(self, *args, **kwargs): Announcement.objects.filter(id=kwargs['id']).update(views=F('views') + 1) return super().get(self, *args, **kwargs)
疑问
- 这种方式未调用Django模型的
save()方法,为何能保存views字段的新值? - 该方式的适用范围是什么?
- 不调用
save()修改模型字段的最佳实践有哪些?
解答
1. 为何不调用save()也能保存字段值
Django的QuerySet.update()方法会直接生成SQL UPDATE语句发送给数据库执行,完全绕开了模型实例的save()方法流程。它不会触发模型的save()信号,也不会执行auto_now这类模型字段的自动逻辑,直接在数据库层面完成字段更新,所以能直接保存views的新值,同时不会修改updated_at。
2. QuerySet.update()的适用范围
- 批量更新多条记录:比如给符合条件的所有公告增加浏览量,用
filter(...).update()比循环实例调用save()效率高得多 - 只需要更新单个/少数字段,且不需要触发模型
save()相关的逻辑(比如信号、字段验证、auto_now更新等) - 避免并发更新问题:用
F()表达式配合update()时,数据库层面会原子性地完成字段自增,不会出现多请求同时更新导致的计数丢失问题(你之前的announcement.views +=1再save()就有并发风险)
3. 不调用save()修改模型字段的最佳实践
- 优先用
QuerySet.update()配合F()表达式处理计数、状态变更等简单字段更新:原子性强,效率高,还能避免触发不必要的模型逻辑 - 如果需要更新单个实例的部分字段且不想触发
auto_now,可以用save(update_fields=['字段名']):比如你最初的场景,也可以改成:
这种方式只会更新指定的字段,announcement = get_object_or_404(Announcement, id=kwargs['id']) announcement.views += 1 announcement.save(update_fields=['views'])auto_now的updated_at不会被修改,同时会触发模型的save()信号(如果有需要的话) - 避免在循环中逐个实例调用
save():数据量大时性能极差,换成批量update()或者bulk_update()更高效 - 使用
bulk_update()处理批量实例更新:当需要更新多个实例的不同字段值时,用bulk_update(实例列表, ['字段1', '字段2']),同样是直接生成批量SQL,比循环save()高效
内容的提问来源于stack exchange,提问作者HTML
相关产品推荐
相关产品推荐

