【发布时间】:2010-12-11 08:14:40
【问题描述】:
如何在 Django 模型中处理并发?我不希望读取相同记录的其他用户覆盖对记录的更改。
【问题讨论】:
标签: python django django-models concurrency
如何在 Django 模型中处理并发?我不希望读取相同记录的其他用户覆盖对记录的更改。
【问题讨论】:
标签: python django django-models concurrency
简短的回答,这确实不是提出的 Django 问题。
并发控制通常作为技术问题提出,但在许多方面是功能需求问题。您希望/需要您的应用程序如何工作?在我们知道这一点之前,很难给出任何特定于 Django 的建议。
但是,我觉得漫无目的,所以就这样吧……
在需要并发控制时,我倾向于问自己两个问题:
如果发生冲突的可能性相对较高,或者丢失修改的影响很大,那么您可能正在考虑某种形式的悲观锁定。在悲观方案中,每个用户必须在打开记录进行修改之前获得一个逻辑锁。
悲观锁定具有很多复杂性。你必须同步对锁的访问,考虑容错,锁过期,锁是否可以被超级用户覆盖,用户可以看到谁拥有锁等等。
在 Django 中,这可以通过单独的 Lock 模型或锁定记录上的某种“锁定用户”外键来实现。使用锁表在存储获取锁的时间、用户、注释等方面为您提供了更多的灵活性。如果您需要一个可用于锁定任何类型记录的通用锁表,请查看django.contrib.contenttypes framework,但很快这会演变为抽象宇航员综合症。
如果不太可能发生冲突或丢失的修改很容易重新创建,那么您可以在功能上摆脱乐观并发技术。这种技术简单且易于实施。本质上,您只需跟踪版本号或修改时间戳,并拒绝您检测到的任何异常修改。
从功能设计的角度来看,您只需考虑这些并发修改错误是如何呈现给您的用户的。
就 Django 而言,乐观并发控制可以通过覆盖模型类的 save 方法来实现...
def save(self, *args, **kwargs):
if self.version != self.read_current_version():
raise ConcurrentModificationError('Ooops!!!!')
super(MyModel, self).save(*args, **kwargs)
当然,要使这些并发机制中的任何一个变得健壮,您必须考虑transactional control。如果您不能保证事务的 ACID 属性,那么这两种模型都不完全可行。
【讨论】:
我认为“保留版本号或时间戳”行不通。
当self.version == self.read_current_version() 为True 时,版本号仍有可能在您调用super().save() 之前被其他会话修改。
【讨论】:
我同意介绍性的explanation from Joe Holloway。
我想为他回答的最后一部分提供一个有效的 sn-p(“就 Django 而言,可以通过覆盖模型类上的 save 方法来实现乐观并发控制......”)
您可以使用以下类作为您自己模型的祖先。
如果您在数据库事务内部(例如,通过在外部范围内使用 transaction.atomic),则以下 Python 语句是安全且一致的
在实践中,通过单次触发,语句 filter + update 在记录上提供了一种 test_and_set:它们验证版本并获取行上的隐式数据库级锁。
因此,以下“保存”能够更新记录的字段,确保它是对该模型实例进行操作的唯一会话。
最终提交(例如由_exit_在transaction.atomic中自动执行)释放行上的隐式数据库级锁:
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)
【讨论】: