Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/database/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Database Django的原子操作?_Database_Django_Concurrency_Locking_Race Condition - Fatal编程技术网

Database Django的原子操作?

Database Django的原子操作?,database,django,concurrency,locking,race-condition,Database,Django,Concurrency,Locking,Race Condition,我试图为计数器实现(我认为是)一个非常简单的数据模型: class VisitorDayTypeCounter(models.Model): visitType = models.CharField(max_length=60) visitDate = models.DateField('Visit Date') counter = models.IntegerField() 当有人通过时,它将查找与visitType和visitDate匹配的行;如果此行不存在,将使用

我试图为计数器实现(我认为是)一个非常简单的数据模型:

class VisitorDayTypeCounter(models.Model):
    visitType = models.CharField(max_length=60)
    visitDate = models.DateField('Visit Date')
    counter = models.IntegerField()
当有人通过时,它将查找与visitType和visitDate匹配的行;如果此行不存在,将使用计数器=0创建该行

然后我们增加计数器并保存

我担心的是,这个过程完全是一场竞赛。两个请求可以同时检查实体是否存在,并且两个请求都可以创建实体。在读取计数器和保存结果之间,可能会出现另一个请求并使其递增(导致计数丢失)

到目前为止,无论是在Django文档中还是在教程中,我都没有找到解决这个问题的好方法(事实上,教程的投票部分似乎有一个种族条件)

如何安全地执行此操作?

两个建议:

将唯一的_添加到模型中,并将创建内容包装到异常处理程序中以捕获重复项:

class VisitorDayTypeCounter(models.Model):
    visitType = models.CharField(max_length=60)
    visitDate = models.DateField('Visit Date')
    counter = models.IntegerField()
    class Meta:
        unique_together = (('visitType', 'visitDate'))

在此之后,计数器的更新中可能会出现一个较小的竞争条件。如果您有足够的流量来关注这一点,我建议您研究事务以获得更细粒度的数据库控制。我不认为ORM直接支持锁定/同步。交易文件可用

为什么不使用数据库作为并发层?将表中的主键或唯一约束添加到visitType和visitDate。如果我没有弄错的话,django在他们的数据库模型类中并不完全支持这一点,或者至少我没有看到一个示例

将约束/键添加到表中后,您所要做的就是:

  • 检查行是否在那里。如果是,就去拿
  • 插入行。如果没有错误,你很好,可以继续前进
  • 如果出现错误(即竞争条件),请重新获取该行。如果没有争吵,那就是一个真正的错误。否则,你就没事了

  • 这样做很讨厌,但似乎足够快,可以覆盖大多数情况。

    您应该使用数据库事务来避免这种竞争情况。事务允许您在“全部或无”的基础上执行创建、读取、递增和保存计数器的整个操作。如果出现任何问题,它将回滚整个事件,您可以再试一次


    查看Django有一个事务中间件,或者您可以使用视图或方法周围的装饰器来创建事务。

    如果您确实希望计数器准确,您可以使用事务,但所需的并发量会在任何重大负载下真正拖累您的应用程序和数据库。相反,您可以考虑采用更具消息传递风格的方法,只需将每次访问的计数记录转储到一个表中,您就可以在其中增加计数器。然后,当您需要访问总数时,在访问表上进行计数。您还可以有一个每天运行任意次数的后台进程,该进程将访问次数相加,然后将其存储在父表中。为了节省空间,它还将删除它汇总的子访问表中的所有记录。如果没有多个代理竞争相同的资源(计数器),那么您将大大降低并发成本。

    这有点麻烦。原始SQL将降低代码的可移植性,但它将消除计数器增量上的竞争条件。理论上,这应该在您执行查询时增加计数器。我还没有对此进行测试,所以您应该确保在查询中正确插入列表

    class VisitorDayTypeCounterManager(models.Manager):
        def get_query_set(self):
            qs = super(VisitorDayTypeCounterManager, self).get_query_set()
    
            from django.db import connection
            cursor = connection.cursor()
    
            pk_list = qs.values_list('id', flat=True)
            cursor.execute('UPDATE table_name SET counter = counter + 1 WHERE id IN %s', [pk_list])
    
            return qs
    
    class VisitorDayTypeCounter(models.Model):
        ...
    
        objects = VisitorDayTypeCounterManager()
    
    您可以使用来自的修补程序来支持数据库级锁定

    使用修补程序,此代码将是原子代码:

    visitors = VisitorDayTypeCounter.objects.get(day=curday).for_update()
    visitors.counter += 1
    visitors.save()
    

    从Django 1.1开始,您可以使用ORM的F()表达式

    from django.db.models import F
    product = Product.objects.get(name='Venezuelan Beaver Cheese')
    product.number_sold = F('number_sold') + 1
    product.save()
    
    有关更多详细信息,请参阅文档:


    独特的组合让我感觉更加舒适。很可能,这条路上不会有足够的流量导致比赛受到影响,但由于我同时在学习Django,我想我想“做得对”。谢谢你的帮助!是的,我听到了。也许这里的其他人会意识到一个ORM功能可以处理这个问题,或者可以清除一些内置的安全性。它不能处理两个人同时去更新计数器的情况。我同意这里的答案似乎是交易,但目前还不清楚该功能是否能真正解决增量问题——获取行的选择仍然会成功,更改计数器值的更新仍然会成功。如果我错了,举个例子就太棒了。你需要在select过程中锁定表才能这样做,正如Sam提到的那样,这会降低你的性能。这是最好的方法,如果你不经常指控柜台。嘿,好电话!我主要是做应用程序引擎的工作,我被“事务只对一个条目起作用”和“做聚合函数非常昂贵”挂断了电话。这是解决问题的一个非常简单的方法。谢谢我想这确实取决于进程是读重还是写重。在我的系统中,计数的读取频率将远高于计数的增量,因此对于前面提到的问题,这可能不是最好的计划。不过,它确实解决了我的其他问题,所以谢谢!根据允许计数的陈旧程度,您可以有一个后台进程,每隔一段时间汇总一次。那么您就不会按照请求进行聚合了。有趣的是,这种日志记录方法与许多数据库中事务的安全性、原子性和可恢复性类似(例如,请参阅Postgresql手册中的实现细节)。数据库是否仍有可能在两个单独的连接上同时执行此查询,并且仍然存在(概率要低得多)竞争条件?这一切都取决于围绕此的隐藏事务