Django模型中的并发控制

24 投票
4 回答
18903 浏览
提问于 2025-04-15 15:30

我该如何在Django模型中处理并发问题?我不希望同一个记录的修改被另一个用户覆盖,尤其是当他们也在查看这个记录的时候。

4 个回答

5

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

我想分享一个与他回答最后部分相关的工作示例(“在Django中,乐观并发控制可以通过重写模型类的保存方法来实现...”)。

你可以使用下面这个类作为你自己模型的基础。

如果你在一个数据库事务中(比如在外部作用域中使用transaction.atomic),以下的Python语句是安全且一致的。

实际上,通过一次操作,这些语句的过滤和更新就像是在记录上进行了一种测试和设置:它们会验证版本并在数据库层面上对这一行进行隐式锁定。

所以,下面的“保存”操作能够更新记录的字段,确保这是唯一一个在操作该模型实例的会话。

最后的提交(例如,通过在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

14

我觉得“保持一个版本号或时间戳”这个方法不太靠谱。

self.version == self.read_current_version() 这个条件为 True 时,还是有可能在你调用 super().save() 之前,版本号被其他会话修改了。

30

简单来说,这个问题其实并不是一个关于Django的问题。

并发控制通常被看作是一个技术性的问题,但从某种程度上来说,它更多的是关于功能需求的问题。你希望或者需要你的应用程序如何工作?在我们弄清楚这一点之前,很难给出任何Django特定的建议。

不过,我想多说几句,所以接着说……

当我遇到需要进行并发控制的情况时,我通常会问自己两个问题:

  • 两个用户同时修改同一条记录的可能性有多大?
  • 如果用户对记录的修改丢失了,会对他/她造成什么影响?

如果发生冲突的可能性相对较高,或者丢失修改的影响很严重,那么你可能需要考虑某种形式的悲观锁。在悲观锁的方案中,每个用户在修改记录之前必须先获取一个逻辑锁。

悲观锁会带来很多复杂性。你需要同步访问这些锁,考虑容错能力,锁的过期时间,超级用户是否可以覆盖锁,用户是否能看到谁拥有锁,等等。

在Django中,这可以通过创建一个单独的锁模型或者在被锁定的记录上添加一个“锁定用户”的外键来实现。使用锁表可以让你在存储锁的获取时间、用户、备注等方面有更多的灵活性。如果你需要一个通用的锁表,可以用来锁定任何类型的记录,那么可以看看django.contrib.contenttypes框架,但这很快可能会变得过于抽象。

如果冲突的可能性不大,或者丢失的修改可以轻松恢复,那么你可以使用乐观并发技术。这种技术简单且更容易实现。基本上,你只需跟踪一个版本号或修改时间戳,并拒绝任何你检测到的异常修改。

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

在Django中,乐观并发控制可以通过重写模型类的保存方法来实现……

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

当然,无论是哪种并发机制,要确保其稳健性,你都需要考虑事务控制。如果不能保证事务的ACID特性,这两种模型都无法完全发挥作用。

撰写回答