ActiveRecord/Rails中的原子插入或增量



在Rails中为带有计数器的模型实现原子插入/更新的最佳方法是什么?对于我试图解决的问题,一个很好的类比是一个带有两个字段的"like"计数器:

url   : string
count : integer

在插入时,如果当前没有匹配url的记录,则应该创建一个计数为1的新记录;否则,现有记录的count字段应该递增。

最初,我尝试了如下代码:

Like.find_or_create_by_url("http://example.com").increment!(:count)
但不出所料,结果SQL显示SELECT发生在UPDATE事务之外:
Like Load (0.4ms)  SELECT `likes`.* FROM `likes` WHERE `likes`.`url` = 'http://example.com' LIMIT 1
(0.1ms)  BEGIN
(0.2ms)  UPDATE `likes` SET `count` = 4, `updated_at` = '2013-01-17 19:41:22' WHERE `likes`.`id` = 2
(1.6ms)  COMMIT

是否有一个Rails习语来处理这个问题,或者我是否需要在SQL级别实现它(从而失去一些可移植性)?

可以使用悲观锁,

like = Like.find_or_create_by_url("http://example.com")
like.with_lock do
    like.increment!(:count)
end

我不知道有任何ActiveRecord方法在单个查询中实现原子增量。你自己的答案是你所能得到的。

所以你的问题可能无法解决使用ActiveRecord。记住:ActiveRecord只是一个映射器,用于简化某些事情,同时使其他事情复杂化。有些问题只能通过对数据库的普通SQL查询来解决。您将失去一些可移植性。下面的例子将在MySQL中工作,但据我所知,不适用于sqlite和其他。

 quoted_url = ActiveRecord::Base.connection.quote(@your_url_here)
::ActiveRecord::Base.connection.execute("INSERT INTO likes SET `count` = 1, url = "#{quoted_url}" ON DUPLICATE KEY UPDATE count = count+1;");

这是我到目前为止所想到的。这假定url列上有唯一的索引。

begin
  Like.create(url: url, count: 1)
  puts "inserted"
rescue ActiveRecord::RecordNotUnique
  id = Like.where("url = ?", url).first.id
  Like.increment_counter(:count, id)
  puts "incremented"
end

Rails的increment_counter方法是原子的,并像下面这样转换为SQL:

UPDATE 'likes' SET 'count' = COALESCE('count', 0) + 1 WHERE 'likes'.'id' = 1

捕捉异常来实现正常的业务逻辑似乎相当丑陋,所以我希望能得到改进的建议。

Rails支持事务,如果这是你正在寻找的,所以:

Like.transaction do
  Like.find_or_create_by_url("http://example.com").increment!(:count)
end

目前为止我最喜欢的解决方案:

Like.increment_counter(
  :count, 
  Like.where(url: url).first_or_create!.id
)

对于这个用例和其他用例,我认为update_all方法是最干净的。

num_updated =
  ModelClass.where(:id             => my_active_record_model.id,
                   :service_status => "queued").
             update_all(:service_status => "in_progress")
if num_updated > 0
  # we updated
else
  # something else updated in the interim
end

不完全是你的例子,只是从链接页面粘贴。但是您可以控制where条件和要更新的内容。非常漂亮的

相关内容

  • 没有找到相关文章

最新更新