如何在Django模型中处理并发性?我不希望对记录的更改被读取同一记录的其他用户覆盖。
b4qexyjb1#
简短的回答是,这真的不是Django提出的问题。并发控制通常被认为是一个技术问题,但在很多方面它是一个功能需求的问题。你希望/需要你的应用程序如何工作?在我们知道之前,很难给予任何Django特有的建议。但是,我想漫无边际,所以这里去...当遇到并发控制的需求时,我倾向于问自己两个问题:
如果冲突的可能性相对较高,或者丢失修改的影响很严重,则您可能会看到某种形式的悲观锁定。在悲观方案中,每个用户在打开记录进行修改之前都必须获取逻辑锁。悲观锁定带来了很多复杂性,必须同步对锁的访问,考虑容错、锁过期、超级用户能否覆盖锁、用户能否看到谁拥有锁等等。在Django中,这可以通过一个单独的Lock模型或者在被锁定的记录上使用某种“lock user”外键来实现。使用锁表在存储获取锁的时间、用户、注解等方面给了你更多的灵活性。如果你需要一个通用的锁表,可以用来锁定任何类型的记录,那么可以看看django.contrib.contenttypes framework,但很快就会演变成抽象宇航员综合症。如果冲突不太可能发生,或者丢失的修改可以轻易地重新创建,那么您可以使用乐观并发技术。这种技术简单且易于实现。本质上,您只需跟踪版本号或修改时间戳,并拒绝任何检测到不正常的修改。从功能设计的Angular 来看,您只需考虑如何将这些并发修改错误呈现给您的用户。在Django中,乐观并发控制可以通过覆盖模型类上的保存方法来实现...
def save(self, *args, **kwargs): if self.version != self.read_current_version(): raise ConcurrentModificationError('Ooops!!!!') super(MyModel, self).save(*args, **kwargs)
当然,要使这两种并发机制中的任何一种都是健壮的,就必须考虑transactional control,如果不能保证事务的ACID属性,这两种模型都不能完全工作。
jtoj6r0c2#
我不认为“保留版本号或时间戳”有效。当self.version == self.read_current_version()为True时,仍有可能在您调用super().save()之前版本号被其他会话修改。
self.version == self.read_current_version()
True
super().save()
bvjxkvbb3#
我同意乔·霍洛威的介绍性解释。我想提供一个与他的回答的最后一部分相关的工作片段(“就Django而言,乐观并发控制可以通过覆盖模型类上的保存方法来实现...”)可以使用以下类作为自己模型的祖先。如果您在数据库事务内部(例如,通过在外部作用域中使用 transaction.atomic),则以下Python语句是安全且一致的在实践中,通过一次触发,语句filter + update在记录上提供了一种test_and_set:它们验证版本并获取该行上的隐式数据库级锁。因此,下面的“保存”操作能够更新记录的字段,确保它是在该模型示例上操作的唯一会话。最后一次提交(例如,由transaction.atomic中的_exit_自动执行)释放该行上的隐式数据库级锁:
class ConcurrentModel(models.Model): _change = models.IntegerField(default=0) class Meta: abstract = True def save(self, *args, **kwargs): cls = self.__class__ if self.pk: rows = cls.objects.filter( pk=self.pk, _change=self._change).update( _change=self._change + 1) if not rows: raise ConcurrentModificationError(cls.__name__, self.pk) self._change += 1 super(ConcurrentModel, self).save(*args, **kwargs)
它取自https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default
yrwegjxp4#
对于并发控制,我使用并推荐悲观锁和select_for_update(),而不是乐观锁,这是Joe霍洛威的答案和Marco De保利的答案。你也可以在Django Admin中看到我下面关于SELECT FOR UPDATE的帖子:
4条答案
按热度按时间b4qexyjb1#
简短的回答是,这真的不是Django提出的问题。
并发控制通常被认为是一个技术问题,但在很多方面它是一个功能需求的问题。你希望/需要你的应用程序如何工作?在我们知道之前,很难给予任何Django特有的建议。
但是,我想漫无边际,所以这里去...
当遇到并发控制的需求时,我倾向于问自己两个问题:
如果冲突的可能性相对较高,或者丢失修改的影响很严重,则您可能会看到某种形式的悲观锁定。在悲观方案中,每个用户在打开记录进行修改之前都必须获取逻辑锁。
悲观锁定带来了很多复杂性,必须同步对锁的访问,考虑容错、锁过期、超级用户能否覆盖锁、用户能否看到谁拥有锁等等。
在Django中,这可以通过一个单独的Lock模型或者在被锁定的记录上使用某种“lock user”外键来实现。使用锁表在存储获取锁的时间、用户、注解等方面给了你更多的灵活性。如果你需要一个通用的锁表,可以用来锁定任何类型的记录,那么可以看看django.contrib.contenttypes framework,但很快就会演变成抽象宇航员综合症。
如果冲突不太可能发生,或者丢失的修改可以轻易地重新创建,那么您可以使用乐观并发技术。这种技术简单且易于实现。本质上,您只需跟踪版本号或修改时间戳,并拒绝任何检测到不正常的修改。
从功能设计的Angular 来看,您只需考虑如何将这些并发修改错误呈现给您的用户。
在Django中,乐观并发控制可以通过覆盖模型类上的保存方法来实现...
当然,要使这两种并发机制中的任何一种都是健壮的,就必须考虑transactional control,如果不能保证事务的ACID属性,这两种模型都不能完全工作。
jtoj6r0c2#
我不认为“保留版本号或时间戳”有效。
当
self.version == self.read_current_version()
为True
时,仍有可能在您调用super().save()
之前版本号被其他会话修改。bvjxkvbb3#
我同意乔·霍洛威的介绍性解释。
我想提供一个与他的回答的最后一部分相关的工作片段(“就Django而言,乐观并发控制可以通过覆盖模型类上的保存方法来实现...”)
可以使用以下类作为自己模型的祖先。
如果您在数据库事务内部(例如,通过在外部作用域中使用 transaction.atomic),则以下Python语句是安全且一致的
在实践中,通过一次触发,语句filter + update在记录上提供了一种test_and_set:它们验证版本并获取该行上的隐式数据库级锁。
因此,下面的“保存”操作能够更新记录的字段,确保它是在该模型示例上操作的唯一会话。
最后一次提交(例如,由transaction.atomic中的_exit_自动执行)释放该行上的隐式数据库级锁:
它取自https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default
yrwegjxp4#
对于并发控制,我使用并推荐悲观锁和select_for_update(),而不是乐观锁,这是Joe霍洛威的答案和Marco De保利的答案。
你也可以在Django Admin中看到我下面关于SELECT FOR UPDATE的帖子: