我有以下模型:
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方法还可以将两个“歌曲”合并成一个,如果用户添加了关联歌词。换句话说,如果用户拥有一首歌的第一行和第三行,直到她添加了同一首歌的第二行,它们被视为两首不同的歌曲。
问题在于,当用户同时添加同一首歌的多个歌词(通过从搜索结果中选择若干个歌词)时,在MySQL中会偶尔发生竞态条件,即尽管这些歌词相邻且应该组合成单个“歌曲”,但会实例化两个歌曲模型。(不幸的是,这样会导致歌词以正确的顺序呈现。)
我已经阅读了无数关于乐观锁定与悲观锁定等的帖子,并尝试了各种选项,但似乎无法摆脱这个问题。每次用户创建歌词时,锁定整个Song表似乎是唯一的预防措施(这似乎对性能产生了巨大影响)。
这是防止此类问题发生的唯一方法吗?我的架构基本上有什么问题吗?我想象这在许多项目中都是一个常见问题,但据我所知,它似乎并不经常出现。似乎每当在after_create方法中实例化父级关联时,如果父模型的创建(在这种情况下为Song)取决于另一个子模型(在这种情况下为Lyric)的存在,则有竞态条件的可能性。