【问题标题】:How do you avoid this race condition in Python / Django / MySQL?在 Python / Django / MySQL 中如何避免这种竞争条件?
【发布时间】:2011-04-08 21:22:07
【问题描述】:

我有一个模型 MyModel,它有一个字段 expire_datetime。

每次用户检索 MyModel 的实例时,我需要先 检查它是否已过期。如果它已经过期,比我需要 增加一些计数器,更新其他的,然后扩展 expiration_datetime 到未来的某个时间。

所以视图会做这样的事情:

if object.expiration_datetime < datetime.datetime.now(): 
    object.counter = F('counter') + 1 
    object.expiration_datetime = F('expiration_datetime') + datetime.timedelta(days=1) 
    object.save() 

上面的代码中有一个竞争条件。说线程 1 检查和 发现当前实例已过期,继续递增 计数器并延长到期日期时间。但在它可以做之前 因此,线程 2 被调度并执行相同的操作。到时间线程 1 终于完成了,计数器增加了两次,并且 expration_datetime 已延长两次。

这看起来应该是一个很常见的问题。处理它的最有效方法是什么?理想情况下,我希望能够以数据库可移植的方式在 Django 中处理它。

【问题讨论】:

    标签: python mysql database django race-condition


    【解决方案1】:

    这可能是optimistic locking 的一个很好的用例。有几种方法可以做到:

    • 您可以有一个版本号,然后运行UPDATE 查询,以便它始终在WHERE 子句中包含版本号,然后检查是否有任何行发生了更改。
    • WHERE 子句中包含记录的每个值(在您进行更改之前),这样您就可以确保您保存的记录与您阅读时完全相同。

    如何在 Django 中实现乐观锁?看看这个问题:Django: How can I protect against concurrent modification of data base entries

    【讨论】:

    • 确实,使用完整的WHERE 子句听起来像是保存前仔​​细检查的好主意。
    【解决方案2】:

    使用数据库事务。它们的设计目的就是像这样处理案件。

    如果您使用 MySQL,请注意只有 InnoDB 表支持 ACID 事务,因此请确保您的表使用 InnoDB 引擎。

    【讨论】:

    • 是的。我不能代表 MySQL,但 SQLite 的默认设置基本上是“一旦在事务上读取数据,其他读取器将阻塞直到事务完成”。因此,一旦您获得了obj.expiration_datetime,就可以保证没有其他人可以阅读它。
    • 澄清一下,在 MySQL 中,您必须使用 SELECT ... FOR UPDATE query 读取数据,这是一种(悲观的)行锁定形式。此时它没有support in Django,因此数据库不可知论将是困难的。
    • 你为什么说你必须先做一个SELECT ... FOR UPDATE?我在 MySQL 文档中找不到任何建议。
    • 因为在事务内部读取数据并不会锁定数据;它只确定数据是数据库的一致快照(即,该快照不会受到外部事务的影响)。在该事务运行时,其他事务仍然可以读取和更新数据。 MySQL 文档很好地解释了这个场景:dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html
    • 确保如果您使用 select...for update 您已禁用自动提交或之前已执行启动事务,否则锁定将被忽略。
    【解决方案3】:

    您还可以使用CaseWhen 条件表达式。 Docs.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-02
      相关资源
      最近更新 更多