我有以下型号:
class Lyric < ActiveRecord::Base
belongs_to :user
belongs_to :song
after_create :add_to_song
end
class Song < ActiveRecord::Base
belongs_to :user
has_many :lyrics
end
这个想法是用户可以为一首歌添加任意数量的歌词。如果为该用户尚不存在的新歌曲输入歌词,则为该用户创建新歌曲。这是通过调用after_create方法'add_to_song'来实现的,该方法检查用户是否有该歌曲的任何歌词:
def add_to_song
sl = self.song_line
# Check for adjacent songs
prior_song = Song.where(:user_id => self.user.id,
:title=> sl.title,
:artist => sl.artist,
:last_line => sl.linenum-1).first
next_song = Song.where(:user_id => self.user.id,
:title=> sl.title,
:artist => sl.artist,
:frst_line => sl.linenum+1).first
# Case 1 - No existing song
if !prior_song && !next_song
song = Song.create!(:user_id => self.user.id,
:length => 1,
:title=> sl.title,
:artist => sl.artist,
:frst_line => sl.linenum,
:last_line => sl.linenum )
self.update_attribute( :song_id, song.id )
# Case 2 - Lyric is between two songs -> merge songs
elsif prior_song && next_song
prior_song.absorb( next_song, self )
# Case 3 - Lyric is new first lyric of existing song
elsif next_song
next_song.expand( self )
# Case 4 - Lyric is new last lyric of existing song
else
prior_song.expand( self )
end
end
如果用户添加链接歌词,则add_to_song方法还会将两首“歌曲”合并为一首。换句话说,如果用户拥有歌曲的第1行和第3行,则在添加同一首歌的第2行之前,它们将被视为两首不同的歌曲。
当用户同时从同一首歌中添加多首歌词时(通过从搜索结果中选择一些歌词),MySQL中偶尔会出现竞争条件,并且即使歌词相邻,也会为同一首歌实例化两首歌曲模型相互之间应该合并成一首'Song'。 (不幸的结果是歌词以正确的顺序呈现。)
我已经阅读了关于乐观与悲观锁定等的无尽帖子,并尝试了各种选项,但似乎无法摆脱问题。每次用户创建一个Lyric时,锁定整个Song表似乎是一种矫枉过正。
这是防止这种情况发生的唯一方法吗? (这似乎是对表演的巨大打击)。 我的架构中是否存在根本错误的问题?我认为这是许多项目中的常见问题,但就我所知,它似乎并不太常见。看来,只要在after_create方法中实例化父关联,如果父模型(在本例中为Song)的创建依赖于另一个子的存在(在这种情况下),则存在竞争条件的可能性。 ,抒情)。
答案 0 :(得分:0)
如果你不想锁定表格,有一种丑陋的方法可以防止这种情况发生:互斥体。
这样的事情:
File.open(MUTEX_FILE_PATH, "w") unless File.exists?(MUTEX_FILE_PATH)
mutex = File.new(MUTEX_FILE_PATH,"r+")
begin
mutex.flock(File::LOCK_EX)
...code...
ensure
mutex.flock(File::LOCK_UN)
end
可以在不阻止整个表的情况下工作。您可以更好地执行性能并使用用户ID创建互斥锁,这样块就可以为每个用户而不是任何人使用。
我没有真正检查下一首歌并查看上一首歌曲的内容,但是如果你通过歌曲做用户has_many歌词并不比你当前的用户通过歌词更好地播放歌曲?