在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,而且芹菜不适合这种解决方案。
'job.status'和'cjob.status'的值是什么?什么类型? – dappawit 2011-03-17 22:25:32
这些是单个字符:Job.EXECUTING =='E'。第5行的查询确实排除了状态为'E'的作业(事实上,它只接受那些'W'或'N')的作业。 – ralphje 2011-03-24 10:54:14