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

时间:2013-01-17 19:53:52

标签: ruby-on-rails-3 activerecord

在Rails中使用计数器实现原型插入/更新的最佳方法是什么?我试图解决的问题的一个很好的类比是一个“喜欢”的计数器有两个字段:

url   : string
count : integer

插入后,如果当前没有匹配网址的记录,则应创建一个计数为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级别实现此功能(从而失去一些可移植性)?

6 个答案:

答案 0 :(得分:8)

您可以使用permissive lock

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

答案 1 :(得分:7)

到目前为止,我已经提出了这个问题。这假定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

捕获异常以实现正常的业务逻辑似乎相当丑陋,所以我希望提出改进建议。

答案 2 :(得分:6)

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

因此,使用ActiveRecord可能无法解决您的问题。请记住:ActiveRecord只是一个简化某些事情的映射器,同时使其他事情变得复杂。一些问题只能由纯SQL查询解决到数据库。你会失去一些便携性。以下示例适用于MySQL,但据我所知,不适用于SQLlite和其他。

 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;");

答案 3 :(得分:0)

Rails支持Transactions,如果这是您正在寻找的内容,那么:

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

答案 4 :(得分:0)

对于此用例和其他用例,我认为update_all approach最干净。

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条件以及要更新的内容。很漂亮

答案 5 :(得分:0)

My favorite solution thus far:

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