python Django 模型中的并发控制

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1645269/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 22:47:35  来源:igfitidea点击:

Concurrency control in Django model

pythondjangodjango-modelsconcurrency

提问by Pablo

How do I handle concurrency in a Django model? I don't want the changes to the record being overwritten by another user who reads the same record.

如何处理 Django 模型中的并发?我不希望对记录的更改被读取同一记录的另一个用户覆盖。

回答by Joe Holloway

The short answer, this really isn't a Django question as presented.

简短的回答,这真的不是所提出的 Django 问题。

Concurrency control is often presented as a technical question, but is in many ways a question of functional requirements. How do you want/need your application to work? Until we know that, it will be difficult to give any Django-specific advice.

并发控制通常作为一个技术问题出现,但在很多方面都是功能需求的问题。您希望/需要您的应用程序如何工作?在我们知道这一点之前,很难给出任何针对 Django 的建议。

But, I feel like rambling, so here goes...

但是,我想漫无边际,所以这里是......

There are two questions that I tend to ask myself when confronted with the need for concurrency control:

在面对并发控制的需求时,我倾向于问自己两个问题:

  • How likely is it that two users will need to concurrently modify the same record?
  • What is the impact to the user if his/her modifications to a record are lost?
  • 两个用户需要同时修改同一个记录的可能性有多大?
  • 如果用户对记录的修改丢失,对用户有什么影响?

If the likelihood of collisions is relatively high, or the impact of losing a modification is severe, then you may be looking at some form of pessimistic locking. In a pessimistic scheme, each user must acquire a logical lock prior to opening the record for modification.

如果冲突的可能性相对较高,或者丢失修改的影响很严重,那么您可能正在考虑某种形式的悲观锁定。在悲观方案中,每个用户必须在打开记录进行修改之前获得一个逻辑锁。

Pessimistic locking comes with much complexity. You must synchronize access to the locks, consider fault tolerance, lock expiration, can locks be overridden by super users, can users see who has the lock, so on and so on.

悲观锁定带来了很多复杂性。你必须同步对锁的访问,考虑容错,锁过期,锁可以被超级用户覆盖,用户可以看到谁拥有锁,等等。

In Django, this could be implemented with a separate Lock model or some kind of 'lock user' foreign key on the locked record. Using a lock table gives you a bit more flexibility in terms of storing when the lock was acquired, user, notes, etc. If you need a generic lock table that can be used to lock any kind of record, then take a look at the django.contrib.contenttypes framework, but quickly this can devolve into abstraction astronaut syndrome.

在 Django 中,这可以通过单独的 Lock 模型或锁定记录上的某种“锁定用户”外键来实现。使用锁表可以让你在获取锁的时间、用户、笔记等方面有更大的灵活性。如果你需要一个可用于锁定任何类型记录的通用锁表,请查看django.contrib.contenttypes 框架,但很快这会演变成抽象宇航员综合症。

If collisions are unlikely or lost modifications are trivially recreated, then you can functionally get away with optimistic concurrency techniques. This technique is simple and easier to implement. Essentially, you just keep track of a version number or modification time stamp and reject any modifications that you detect as out of whack.

如果不太可能发生冲突或丢失的修改可以轻松地重新创建,那么您可以在功能上使用乐观并发技术。这种技术很简单,也更容易实现。本质上,您只需跟踪版本号或修改时间戳,并拒绝您检测到的任何不正常的修改。

From a functional design standpoint, you only have to consider how these concurrent modification errors are presented to your users.

从功能设计的角度来看,您只需考虑如何将这些并发修改错误呈现给您的用户。

In terms of Django, optimistic concurrency control can be implemented by overriding the save method on your model class...

在Django方面,可以通过覆盖模型类上的save方法来实现乐观并发控制......

def save(self, *args, **kwargs):
    if self.version != self.read_current_version():
        raise ConcurrentModificationError('Ooops!!!!')
    super(MyModel, self).save(*args, **kwargs)

And, of course, for either of these concurrency mechanisms to be robust, you have to consider transactional control. Neither of these models are fully workable if you can't guarantee ACID properties of your transactions.

而且,当然,要使这些并发机制中的任何一个变得健壮,您都必须考虑事务控制。如果您不能保证交易的 ACID 属性,那么这些模型都不是完全可行的。

回答by ivan

I don't think that 'keeping a version number or timestamp' works.

我认为“保留版本号或时间戳”行不通。

When self.version == self.read_current_version()is True, there is still a chance that the version number got modified by other sessions just before you call super().save().

self.version == self.read_current_version()是时True,版本号仍有可能在您调用之前被其他会话修改super().save()

回答by Marco De Paoli

I agree with the introductory explanation from Joe Holloway.

我同意 Joe Holloway 的介绍性解释。

I want to contribute with a working snippet relative to the very last part of his answer ("In terms of Django, optimistic concurrency control can be implemented by overriding the save method on your model class...")

我想贡献一个与他的答案的最后一部分相关的工作片段(“就 Django 而言,可以通过覆盖模型类上的 save 方法来实现乐观并发控制......”)

You can use the following class as an ancestor for your own model

您可以使用以下类作为您自己模型的祖先

If you are inside a db transaction (ex. by using transaction.atomic in an outer scope) the following python statements are safe and consistent

如果您在 db 事务中(例如,通过在外部范围内使用 transaction.atomic),则以下 python 语句是安全且一致的

In practice via one single shot, the statements filter+update provide a sort of test_and_set on the record: they verify the version and acquire an implicitly db-level lock on the row. So the following "save" is able to update fields of the record sure it is the only session which operates on that model instance. The final commit (for example executed automatically by __exit__ in transaction.atomic) releases the implicit db-level lock on the row

在实践中,通过一个单一的镜头,语句 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)

It is taken from https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default

它取自 https://bitbucket.org/depaolim/optlock/src/ced097dc35d3b190eb2ae19853c2348740bc7632/optimistic_lock/models.py?at=default