如何避免竞争条件和锁定等待超时更新页面视图



在 Rails 应用程序中,用户访问我显示弹出窗口的页面。我想在用户每次看到该弹出窗口时更新记录。

为了避免竞争条件,我使用乐观锁定(所以我在弹出表中添加了一个名为 lock_version 的字段)。

代码很简单:

# inside pages/show.html.erb
<%= render @popup %>
# and inside the popup partial...
...
<%
  Popup.transaction do
     begin
       popup.update_attributes(:views => popup.views + 1)
     rescue ActiveRecord::StaleObjectError
       retry
     end
  end
%>

问题是很多用户访问该页面,并且mysql超过了锁定超时。

所以网站冻结了,我收到了很多这些错误:超出锁定等待超时;尝试重新启动事务

这是因为有许多挂起的请求试图使用过时的lock_version值更新记录。

如何解决我的问题?

您可以使用

increment_counter,因为它会生成一个 SQL UPDATE 查询而不会锁定。

但我认为在您的情况下,使用任何键值数据库(如 Redis)来存储和更新您的弹出计数器会更好,因为它可以比 SQL DB 更快地完成。

如果您不能采用@maxd回复中指出的方法,则可以利用异步库(例如 Sidekiq)来处理此类请求(其中它们只会在作业队列中备份)。

lib/some_made_up_class.rb

def increment_popup(popup)
  Popup.transaction do
    begin
       popup.update_attributes(:views => popup.views + 1)
     rescue ActiveRecord::StaleObjectError
       retry
     end
  end
end

然后,在另一段代码(控制器、服务或视图(不太理想地将逻辑放在视图层)中。

SomeMadeUpClass.delay.increment_popup(popup)
# OR you can schedule it
SomeMadeUpClass.delay_for(1.second).increment_popup(popup)

从本质上讲,这将产生排队插入的效果,同时释放您的页面,并且理论上有助于减少您点击的超时等。

虽然它肯定不仅仅是添加像 Sidekiq 这样的库(gem)和我在这里的示例代码,但我认为异步库/工具将有很大帮助。

最新更新