mysql - 防止 Rails 父关系中的竞争条件

标签 mysql ruby-on-rails ruby locking race-condition

我有以下模型:

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)的创建依赖于另一个子模型(在本例中)的存在,则有可能出现竞争条件, 歌词).

最佳答案

如果您不想锁定表,有一种丑陋的方法可以防止这种情况发生:互斥锁。

像这样:

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 创建互斥锁,这样该 block 将适用于每个用户,而不是任何人。

我并没有真正了解检查下一首歌曲和检查上一首歌曲的事情,但是如果您这样做了用户 has_many lyrics through songs 不会比您当前的用户 has_many songs through lyrics 更好?

关于mysql - 防止 Rails 父关系中的竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15560592/

相关文章:

ruby-on-rails - 在Omniauth和Ruby on Rails 3中使用YouTube登录

ruby-on-rails - Ruby 中有没有一种方法可以使用 public_methods 设置类成员的值

ruby - 直到循环卡住;行为怪异?

mysql - 如何关闭 RMySQL 中的结果集?

MySql 查询时间过长

c# - 同时连接MySql/C#

javascript - jQuery 将参数发送到 Rails 应用程序以保存在数据库中

javascript - 使用 Gmaps4rails v2 在 map 加载后添加和更新标记

ruby-on-rails - Ruby on Rails 正在消亡吗?

Ruby 生成的 makefile 不运行