Geo*_*old 9 activerecord ruby-on-rails-3
在Rails中使用计数器为模型实现原子插入/更新的最佳方法是什么?我试图解决的问题的一个很好的比喻是一个"喜欢"的计数器,有两个字段:
url : string
count : integer
Run Code Online (Sandbox Code Playgroud)
插入时,如果当前没有匹配url的记录,则应创建一个计数为1的新记录; 否则现有记录的count字段应递增.
最初我尝试了如下代码:
Like.find_or_create_by_url("http://example.com").increment!(:count)
Run Code Online (Sandbox Code Playgroud)
但不出所料,生成的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
Run Code Online (Sandbox Code Playgroud)
是否存在用于处理此问题的Rails习惯用法,或者我是否需要在SQL级别实现此功能(从而失去一些可移植性)?
小智 9
你可以使用许可锁,
like = Like.find_or_create_by_url("http://example.com")
like.with_lock do
like.increment!(:count)
end
Run Code Online (Sandbox Code Playgroud)
这是我到目前为止所提出的.这假设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
Run Code Online (Sandbox Code Playgroud)
Rails的increment_counter方法是原子的,并转换为SQL,如下所示:
UPDATE 'likes' SET 'count' = COALESCE('count', 0) + 1 WHERE 'likes'.'id' = 1
Run Code Online (Sandbox Code Playgroud)
捕获异常以实现正常的业务逻辑似乎相当丑陋,所以我希望得到改进的建议.
我不知道任何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;");
Run Code Online (Sandbox Code Playgroud)