Django中的竞争条件
在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查询之前先执行一个复杂的查询。
我不太明白这会如何导致我们看到的问题。我们先执行查询,然后有一个语句会在另一个进程持有任务锁的时候阻塞。只有在任务状态被改变后,锁才会被释放。第二个进程现在获得了锁,但任务的状态已经改变,所以它从方法中返回,之后又重新进入;但是cjob
查询不会再返回同一个任务,因为它的状态现在被过滤掉了。
我是不是误解了FOR UPDATE这个条款,还是我漏掉了什么其他的东西?
需要说明的是,我使用的是MySQL和InnoDB,并且Celery不适合这个解决方案。
1 个回答
0
这个问题是通过手动更新事务来解决的。看起来在事务开始后,查询集(QuerySet)没有更新。当两个查询集同时开始,并且有一个任务在这两个查询集中都发生时,就会导致执行者出错。
在阅读了这个回答后,我想出了一个解决办法:在return True
之前,先提交事务。