加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

ruby-on-rails-3 – SELECT FOR UPDATE不会阻止Rails 3.2.11与Po

发布时间:2020-12-17 02:38:01 所属栏目:百科 来源:网络整理
导读:我试图用悲观的锁来避免竞争条件.我期待后一个线程通过SELECT FOR UPDATE获取一行,另一个寻找同一行的线程将被阻塞,直到锁被释放.但是,在测试时,似乎锁定不成立,第二个线程只能获取行并更新它,即使第一个线程尚未保存(更新)该行. 以下是相关代码: 数据库架
我试图用悲观的锁来避免竞争条件.我期待后一个线程通过SELECT FOR UPDATE获取一行,另一个寻找同一行的线程将被阻塞,直到锁被释放.但是,在测试时,似乎锁定不成立,第二个线程只能获取行并更新它,即使第一个线程尚未保存(更新)该行.

以下是相关代码:

数据库架构

class CreateMytables < ActiveRecord::Migration
  def change
    create_table :mytables do |t|
        t.integer  :myID
        t.integer  :attribute1
        t.timestamps
    end

    add_index :mytables,:myID,:unique => true

  end
end

mytables_controller.rb

class MytablessController < ApplicationController

    require 'timeout'

    def create
        myID = Integer(params[:myID])
        begin
            mytable = nil
            Timeout.timeout(25) do 
                p "waiting for lock"              
                mytable = Mytables.find(:first,:conditions => ['"myID" = ?',myID],:lock => true ) #'FOR UPDATE NOWAIT') #true) 
                #mytable.lock!
                p "acquired lock"                 
            end
            if mytable.nil?
                mytable = Mytables.new
                mytable.myID =  myID
            else
                if mytable.attribute1 > Integer(params[:attribute1])
                    respond_to do |format|
                        format.json{
                            render :json => "{"Error": "Update failed,a higher attribute1 value already exist!","Error Code": "C"
}"
                            }
                    end
                    return
                end
            end
            mytable.attribute1 =  Integer(params[:attribute1])           
            sleep 15  #1 
            p "woke up from sleep"
            mytable.save! 
            p "done saving"             
            respond_to do |format|
                format.json{
                          render :json => "{"Success": "Update successful!","Error Code": "A"
}"
                            }
            end
        rescue ActiveRecord::RecordNotUnique #=> e     
            respond_to do |format|
                format.json{
                            render :json => "{"Error": "Update Contention,please retry in a moment!","Error Code": "B"
}"
                            }
            end
        rescue Timeout::Error
            p "Time out error!!!"
            respond_to do |format|
                format.json{
                            render :json => "{"Error": "Update Contention,"Error Code": "B"
}"
                            }
            end
        end   
    end
end

我已经在两个设置中测试了它,一个是在Heroku上使用unicorn和worker_processes 4运行应用程序,另一个是在我的机器上本地运行PostgreSQL 9.1设置,运行应用程序的两个单线程实例,一个是rails server -p 3001,另一个是瘦启动(出于某种原因,如果我只运行rails server或thin start,它们将只按顺序处理传入的调用).

设置1:
数据库中感兴趣的myID的原始attribute1值是3302.我向Heroku应用程序发出了一个更新调用(将attribute1更新为值3303),然后等待大约5秒并向Heroku应用程序发出另一个更新(更新) attribute1到值3304).我期待第二个呼叫大约需要25秒才能完成,因为第一个呼叫需要15秒才能完成,因为我在mytable.save!之前的代码中引入了sleep 15命令,第二个呼叫应该在mytable线路上被阻止= Mytables.find(:first,:conditions => [‘“myID”=?’,:lock => true)约10秒,然后获取锁定然后再睡眠15秒.
但事实证明,第二次通话仅比第一次通话晚了约5秒.

并且如果我反转请求顺序,即第一次调用是将attribute1更新为3304并且5秒延迟的第二次调用是将attribute1更新为3303,则最终值将是3303.
看着Heroku上的日志,第二个呼叫等待没有时间获得锁定,而理论上第一个呼叫正在休眠,因此仍然保持锁定.

设置2:
运行相同应用程序的两个Thin rails服务器,一个在端口3000上,另一个在端口3001上.我的理解是它们连接到同一个数据库,因此如果服务器的一个实例通过SELECT FOR UPDATE获取锁定,则另一个实例应该无法获得锁定并将被阻止.但是,锁的行为与Heroku相同(不按我的意图工作).并且由于服务器在本地运行,我设法执行额外的调整测试,以便在第一个呼叫休眠15秒时,我在启动第二个呼叫之前更改了代码,以便5秒后的第二个呼叫仅睡眠1获得锁定之后的第二次,第二次通话比第一次通话早完成…

我也尝试使用SELECT FOR UPDATE NOWAIT并引入额外的行mytable.lock! SELECT FOR UPDATE行后立即,但结果是一样的.

所以在我看来,当SELECT FOR UPDATE命令成功发布到PostgreSQL表时,其他线程/进程仍然可以SELECT FOR UPDATE同一行,甚至更新同一行而不会阻塞…

我完全感到困惑,任何建议都会受到欢迎.谢谢!

P.S.1我在行上使用锁的原因是我的代码应该能够确保只有将行更新为更高的attribute1值的调用才能成功.

P.S.2本地日志的示例SQL输出

"waiting for lock"
  Mytables Load (4.6ms)  SELECT "mytables".* FROM "mytables" WHERE ("myID" = 1935701094) LIMIT 1 FOR UPDATE
"acquired lock"
"woke up from sleep"
   (0.3ms)  BEGIN
   (1.5ms)  UPDATE "mytables" SET "attribute1" = 3304,"updated_at" = '2013-02-02 13:37:04.425577' WHERE "mytables"."id" = 40
   (0.4ms)  COMMIT
"done saving"

解决方法

事实证明,因为PostGreSQL已经默认启用了自动提交,
这条线

Mytables Load (4.6ms)  SELECT "mytables".* FROM "mytables" WHERE ("myID" = 1935701094) LIMIT 1 FOR UPDATE

实际上后面是自动提交,因此释放锁定.

从这个页面阅读时我错了
http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
那个

.find(____,:lock => true)

方法自动打开一个事务,类似于

.with_lock(lock = true)

覆盖在同一页的末尾……

所以要修复我的Rails代码,我只需要通过添加将其包装在事务中

Mytables.transaction do

begin

并在“救援”线之前添加额外的“结束”.

生成的SQL输出更像是:

(0.3ms)  BEGIN
Mytables Load (4.6ms)  SELECT "mytables".* FROM "mytables" WHERE ("myID" = 1935701094) LIMIT 1 FOR UPDATE
(1.5ms)  UPDATE "mytables" SET "attribute1" = 3304,"updated_at" = '2013-02-02 13:37:04.425577' WHERE "mytables"."id" = 40
(0.4ms)  COMMIT

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读