Django的竞争条件

时间:2011-03-17 18:43:11

标签: python django race-condition

在Django,我遇到了一些严重的竞争条件。当两个跑步者试图同时执行some_method()时,麻烦就开始了。创建的日志记录如下:

Job 3: Candidate
Job 3: Already taken
Job 3: Candidate
Job 3: Already taken
Job 3: Candidate
Job 3: Already taken
(et cetera for 18 MB)

以下方法给我带来麻烦。应该注意的是,该方法将重新运行,直到该方法返回False

def some_method():
    conditions = #(amongst others, excludes jobs with status EXECUTING)

    try:
        cjob = Job.objects.filter(conditions).order_by(some_fields)[0]
    except IndexError:
        return False

    print 'Job %s: Candidate' % cjob.id

    job = cjob.for_update()

    if cjob.status != job.status:
        print 'Job %s: Already taken' % cjob.id
        return True

    print 'Job %s: Starting...' % job.id

    job.status = Job.EXECUTING
    job.save()
    # Critical section

# In models.py:
class Job(models.Model):
    # ...

    def for_update(self):
        return Job.objects.raw('SELECT * FROM `backend_job` WHERE `id` = %s FOR UPDATE', (self.id, ))[0]

目前,Django没有专用的for_update方法,并且为了防止创建具有我们用来确定是否必须运行作业的所有条件的查询,我们在简单的FOR UPDATE查询之前执行困难的查询。

我真的没有看到这会如何导致我们看到的麻烦,我们进行查询,然后是阻止另一个跑步者持有工作锁定的语句。锁定仅在作业状态更改后才会释放。第二个跑步者现在获得锁定,但作业的状态已更改,因此它从方法返回,仅在以后重新输入;但是cjob - 查询不会再次返回相同的作业,因为它的状态现在被过滤器排除了。

我是否误解了FOR UPDATE子句,或者我错过了其他内容?

应该注意的是,我使用带有InnoDB的MySQL,并且Celery不适合这个解决方案。

1 个答案:

答案 0 :(得分:0)

通过手动更新交易解决了该问题。似乎自事务开始以来QuerySet没有更新。当两个QuerySets以某种方式同时启动,并且在两个QuerySet中都会发生一个作业时,它会分解运行者。

在阅读this answer之后,我想出了一个解决方案:在return True之前,提交了交易。