ruby-on-rails-3 – SELECT FOR UPDATE不会阻止Rails 3.2.11与Po
我试图用悲观的锁来避免竞争条件.我期待后一个线程通过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: 并且如果我反转请求顺序,即第一次调用是将attribute1更新为3304并且5秒延迟的第二次调用是将attribute1更新为3303,则最终值将是3303. 设置2: 我也尝试使用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 实际上后面是自动提交,因此释放锁定. 从这个页面阅读时我错了 .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 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |