2011-03-17 48 views
0

在Django,我遇到了一些严重的比赛情况。当两名跑步者同时尝试执行some_method()时,麻烦就开始了。创建的日志如下:Django的比赛条件

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-query之前执行困难的查询。

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

我误解了FOR UPDATE-clause,还是错过了别的东西?

需要注意的是,我在InnoDB中使用MySQL,而且芹菜不适合这种解决方案。

+0

'job.status'和'cjob.status'的值是什么?什么类型? – dappawit 2011-03-17 22:25:32

+0

这些是单个字符:Job.EXECUTING =='E'。第5行的查询确实排除了状态为'E'的作业(事实上,它只接受那些'W'或'N')的作业。 – ralphje 2011-03-24 10:54:14

回答

0

问题已通过手动更新事务得到解决。似乎从事务开始以来QuerySet没有更新。当两个QuerySets以某种方式同时启动时,两个QuerySets中都会发生一项工作,这将打乱跑步者。

看完this answer之后,我想出了一个解决方案:就在return True之前,事务就落实了。