Django:锁定表中的特定行

时间:2012-08-10 19:15:06

标签: database django locking atomic

我有以下django方法:

def setCurrentSong(request, player):    
  try:
     newCurrentSong = ActivePlaylistEntry.objects.get(
       song__player_lib_song_id=request.POST['lib_id'],
       song__player=player,   
       state=u'QE')
   except ObjectDoesNotExist:
     toReturn = HttpResponseNotFound()
     toReturn[MISSING_RESOURCE_HEADER] = 'song'    
     return toReturn

   try:
     currentSong = ActivePlaylistEntry.objects.get(song__player=player, state=u'PL')
     currentSong.state=u'FN'  
     currentSong.save()
   except ObjectDoesNotExist:  
     pass
   except MultipleObjectsReturned:     
     #This is bad. It means that
     #this function isn't getting executed atomically like we hoped it would be
     #I think we may actually need a mutex to protect this critial section :(
     ActivePlaylistEntry.objects.filter(song__player=player, state=u'PL').update(state=u'FN')

   newCurrentSong.state = u'PL'
   newCurrentSong.save()
   PlaylistEntryTimePlayed(playlist_entry=newCurrentSong).save()
   return HttpResponse("Song changed")

基本上,我希望它是这样的,对于给定的player,在任何给定时间只有一个ActivePlaylistEntry具有'PL'(播放)状态。但是,我有实际经历过的情况,由于连续两次快速调用此方法,我为同一个播放器获得两首歌曲,状态为'PL'。这很糟糕,因为我有其他应用程序逻辑,它依赖于玩家在任何给定时间只有一首播放歌曲的事实(加上语义上,在同一个播放器上同时播放两首不同的歌曲没有意义) 。有没有办法让我以原子方式进行更新?仅使用on_commit_success装饰器作为事务运行该方法似乎不起作用。是否有办法锁定属于特定播放器的所有歌曲的表格?我正在考虑在我的模型(布尔字段)中添加一个lock列,并且只是旋转它或暂停线程几毫秒并再次检查,但这些感觉超级hackish和脏。我也在考虑创建一个存储过程,但这并不是真正独立于数据库的。

1 个答案:

答案 0 :(得分:11)

1.4中添加了

Locking queries

with transaction.commit_manually():
  ActivePlayListEntry.objects.select_for_update().filter(...)
  aple = ActivePlayListEntry.objects.get(...)
  aple.state = ...
  transaction.commit()

但你应该考虑重构,以便用ForeignKey的单独表格来表示“活跃”的歌曲。