防止Django在并发请求中多次将同一对象保存到数据库

时间:2012-09-21 13:41:04

标签: database django model save simultaneous

对于我们的博客平台,我们有一个“文章”模型,其中包含“更新的”日期时间字段:

class Article(models.Model):
    updated = models.DateTimeField(null=True, blank=True)
    ...

当文章在24小时内第一次被任何访问者打开时,我们会对不同的模型字段进行一些耗时的计算,然后将模型保存到数据库中。有了这个,我们还将“updated”字段更新为当前的datetime.now()。

if (datetime.now() - article.updated).days > 1:
    # do some time consuming calculations
    article.updated = datetime.now()
    article.save()

当或多或少同时请求文章时,第一个请求上的耗时操作尚未完成,但是,导致每天一次的操作再次在同一个对象上启动(article.updated仍然是旧的值)。是否可以在开始计算之前另外调用article.save()?或者这个数据从保存到数据库推迟到请求完成之后?

3 个答案:

答案 0 :(得分:2)

简短版本:

@transaction.commit_on_success
def update_article( article_id ):
    article = Article.objects.select_for_update().get( pk = article_id )
    if (datetime.now() - article.updated).days > 1:
        # do some time consuming calculations
        article.updated = datetime.now()
        article.save()

select_for_update()锁定数据库行(ID为article_ID的文章)。 该行在交易结束时解锁,位于 自update_article()包裹@transaction.commit_on_success以来函数的结尾。

诗 :自Django 1.4起可用

答案 1 :(得分:1)

使用Django 1.4中引入的查询集select_for_update,它在数据库中执行行级锁定。所有匹配的条目将被锁定,直到事务块结束,这意味着将阻止其他事务更改或获取锁定。有一些特定于datgabase后端的陷阱,所以在完全依赖它之前一定要读取并测试它。

独立于实现的其他一些方法是将模型自定义为具有locked布尔属性。不是很整洁但是可行的解决方案。见What is the simplest way to lock an object in Django

答案 2 :(得分:1)

一些建议:

  • 最好将耗时的计算从请求 - 响应周期转移到后台。可以在此处使用消息队列(如流行的celery)。我认为这是最好的解决方案,但它可能需要一些额外的管理,这对于简单的任务来说可能是过度的;
  • 如果使用缓存,则可以设置对象被锁定的标志。如果缓存对于不同的解释器(如memcached)是常见的,即使你有许多运行你的应用程序的Python解释器,它也会起作用;
  • 您可以安排更新程序(使用cron和自定义Django管理命令)来更新> 24小时前更新的所有对象。除非你有大量的物品和相当长的处理时间,否则它会起作用。