一尘不染

在Django中如何处理这种比赛情况?

django

该代码应获取或创建一个对象,并在必要时对其进行更新。该代码已在网站上投入生产。

在某些情况下-当数据库繁忙时-它会引发异常“ DoesNotExist:MyObj匹配查询不存在”。

# Model:
class MyObj(models.Model):
    thing = models.ForeignKey(Thing)
    owner = models.ForeignKey(User)
    state = models.BooleanField()
    class Meta:
        unique_together = (('thing', 'owner'),)

# Update or create myobj
@transaction.commit_on_success
def create_or_update_myobj(owner, thing, state)
    try:
        myobj, created = MyObj.objects.get_or_create(owner=user,thing=thing)

    except IntegrityError:
        myobj = MyObj.objects.get(owner=user,thing=thing)
        # Will sometimes throw "DoesNotExist: MyObj matching query does not exist"

    myobj.state = state
    myobj.save()

我在ubuntu上使用了一个innodb mysql数据库。

我如何安全地处理此问题?


阅读 333

收藏
2020-03-27

共1个答案

一尘不染

基本上get_or_create 可能会失败 -如果你查看其来源,就会发现它是:get,if-problem:save + some_trickery,if-still-problem:再次获取,if-still-problem:投降并加薪。

这意味着,如果有两个同时运行的线程(或进程)同时运行create_or_update_myobj,都试图get_or_create相同的对象,则:

  • 第一个线程试图获取它-但它尚不存在,
  • 因此,线程尝试创建它,但是在创建对象之前…
  • …第二个线程试图获取它-这显然失败了
  • 现在,由于MySQLdb数据库连接的默认AUTOCOMMIT = OFF和REPEATABLE READ可序列化级别,两个线程都冻结了它们对MyObj表的视图。
  • 随后,第一个线程创建其对象并优雅地返回它,但是…
  • …第二个线程无法创建任何东西,因为它将违反unique约束
  • 有趣的是,get由于MyObj表的冻结视图,因此第二个线程的后续操作看不到在第一个线程中创建的对象

因此,如果你想安全地进行get_or_create任何操作,请尝试以下操作:

 @transaction.commit_on_success
 def my_get_or_create(...):
     try:
         obj = MyObj.objects.create(...)
     except IntegrityError:
         transaction.commit()
         obj = MyObj.objects.get(...)
     return obj

该问题还有第二种解决方案-使用READ COMMITED隔离级别,而不是REPEATABLE READ。但是它的测试较少(至少在MySQL中是这样),因此可能存在更多的错误/问题-但至少它允许将视图绑定到事务,而无需在中间提交。

2020-03-27