gpt4 book ai didi

ruby-on-rails-3 - SELECT FOR UPDATE 不会在带有 PostgreSQL 9.1 的 Rails 3.2.11 上阻塞

转载 作者:行者123 更新时间:2023-12-05 01:10:55 25 4
gpt4 key购买 nike

我正在尝试使用悲观锁来避免竞争条件。我期待在一个线程通过 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, please retry in a moment!\",
\"Error Code\": \"B\"
}"
}
end
end
end
end

我已经在两种设置中对其进行了测试,一种是使用 worker_processes 4 运行带有 unicorn 的应用程序在 Heroku 上,另一个在我的机器上本地运行,设置了 PostgreSQL 9.1,运行应用程序的两个单线程实例,一个是 rails server -p 3001 ,另一个是 thin start (出于某种原因,如果我只运行 rails serverthin start,它们将仅按顺序处理来电)。

设置 1:
数据库中感兴趣的 myID 的原始属性 1 值是 3302。我向 Heroku 应用程序发出了一个更新调用(将属性 1 更新为值 3303),然后等待大约 5 秒钟,然后向 Heroku 应用程序发出另一个调用(更新属性 1 到值 3304)。我预计第二个调用需要大约 25 秒才能完成,因为第一个调用需要 15 秒才能完成,因为 sleep 15我之前在代码中引入的命令 mytable.save! ,第二个调用应该被阻塞在行 mytable = Mytables.find(:first, :conditions => ['"myID" = ?', myID], :lock => true )大约 10 秒,在它获得锁之前,然后休眠 15 秒。
但事实证明,第二次调用只比第一次调用晚了大约 5 秒。

如果我颠倒请求顺序,即第一次调用是将属性 1 更新为 3304,而延迟 5 秒的第二次调用是将属性 1 更新为 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同一行,甚至 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

关于ruby-on-rails-3 - SELECT FOR UPDATE 不会在带有 PostgreSQL 9.1 的 Rails 3.2.11 上阻塞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14662675/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com