为什么activerecord乐观锁定每行只能工作一次?

时间:2021-11-10 06:50:52

Somehow, I always get these on Fridays.

不知何故,我总是在星期五得到这些。

My earlier question was regarding the same problem, but I can now narrow things down a bit:

我之前的问题是关于同样的问题,但我现在可以稍微缩小一点:

I've been playing with this all day, trying to make sense of it. I have a table with a lock_version colum, specified thus:

我整天都在玩这个,试图理解它。我有一个带有lock_version列的表,由此指定:

add_column :jobs, :lock_version, :integer, :default=>0

And I do something like this:

我做这样的事情:

foo = job.create!
first = Job.find(foo.id)
second = Job.find(foo.id)

I then verify that first and second refer to the same object - their ids are the same and I see that row in the database using the mysql command-line tool.

然后我验证第一个和第二个引用相同的对象 - 它们的ID是相同的,我使用mysql命令行工具在数据库中看到该行。

first.some_attribute_field = 'first'
second.some_attribute_field = 'second'
first.save
second.save

no problem so far. I correctly get an ActiveRecord::StaleObjectError exception. HOWEVER:

到目前为止没问题。我正确得到一个ActiveRecord :: StaleObjectError异常。然而:

first = Job.find(foo.id)
second = Job.find(foo.id)
first.some_attribute_field = 'first'
second.some_attribute_field = 'second'
first.save
second.save

...and nothing happens. It turns out that the only time I get correct (thrown exception) behavior is when first and second have a lock_version of 0. After the first save, though, it is NEVER 0 again. What on earth is up with this?

......没有任何反应。事实证明,我唯一一次得到正确(抛出异常)行为是第一次和第二次的lock_version为0.然而,在第一次保存之后,它再次为0。究竟是什么呢?

I'm using ruby 1.8.6 and active record 2.2.2

我正在使用ruby 1.8.6和活动记录2.2.2

Thanks...

3 个解决方案

#1


when you call first.save the second time the value of some_attribute_field is already equal to "first", activerecord knows this so it doesn't update in the db to lock_version doesn't get incremented. The second save works as the db was never changed with the "first".

当你第一次调用some.sat第二次时,some_attribute_field的值已经等于“first”,activerecord知道这一点,所以它不会在db中更新到lock_version不会增加。第二次保存工作,因为db从未使用“first”进行更改。

Try changing the value in the second test to something other than "first" so that it is different to what is in the db.

尝试将第二个测试中的值更改为“first”以外的值,以使其与db中的值不同。

#2


I'm not a Ruby guy, but optimistic locking is familiar for me, so I'll try to help you to debug it.

我不是Ruby人,但乐观锁定对我来说很熟悉,所以我会尽力帮你调试它。

I presume the second save actually updates the database. If both objects have different lock_version AND lock_version is used in UPDATE, it's simply not possible (UPDATE would update zero rows). So, we have only two alternatives:

我假设第二个保存实际上更新了数据库。如果两个对象具有不同的lock_version并且在UPDATE中使用了lock_version,则根本不可能(UPDATE将更新零行)。所以,我们只有两种选择:

  • lock_version is not used in UPDATE statement or used incorrectly
  • lock_version未在UPDATE语句中使用或未正确使用

  • both objects got the same lock_version somehow
  • 两个对象以某种方式得到了相同的lock_version

(actually, there is a third alternative: both save() are in their own transaction, but I feel you have AUTOCOMMIT=true)

(实际上,还有第三种选择:save()都在他们自己的事务中,但我觉得你有AUTOCOMMIT = true)

Can you make actual SQL statements visible? The update statement should read something like

你能看到实际的SQL语句吗? update语句应该是这样的

... WHERE JOB_ID=123 AND LOCK_VERSION=8

When you'll have the actually queries at hand, it would be much easier to make sense of what's going on.

当你手头有实际的查询时,就可以更容易地了解正在发生的事情。

P.S. and one more: in your example in another topic you have this object:

附:还有一个:在你的另一个主题的例子中你有这个对象:

#<Job id: 323, lock: 8, worker_host: "second">

The save() call may be ignored by container if the properties are not changed comparing to load time. I don't know if ActiveRecord has this optimization or not though. But if it does, then second save() is ignored, and optimistic locking doesn't have a chance to kick in.

如果与加载时间相比未更改属性,则容器可以忽略save()调用。我不知道ActiveRecord是否有这种优化。但如果确实如此,则忽略第二个save(),并且乐观锁定没有机会启动。

#3


As Vladimir said, your test/example code is a bit flawed. First doesn't get stored in the datebase during the second save!() because no attributes have changed. See the following example:

正如弗拉基米尔所说,你的测试/示例代码有点缺陷。在第二次保存期间,首先不会存储在日期库中!()因为没有更改任何属性。请参阅以下示例:

foo = Account.create!

first = Account.find(foo.id)
first.cash = 100
first.save!


first = Account.find(foo.id)
first.cash = 100

puts "First lock before " + first.lock_version.to_s
first.save!
puts "First lock after " + first.lock_version.to_s

this produces:

% script/runner another_tester.rb                               
First lock before 1
First lock after 1

Using your example in the 2.3.2 version of rails works as it should with a Stale object exception when second is saved (both times!)

使用2.3.2版本的rails中的示例可以在保存第二个时使用Stale对象异常(两次都是!)

#1


when you call first.save the second time the value of some_attribute_field is already equal to "first", activerecord knows this so it doesn't update in the db to lock_version doesn't get incremented. The second save works as the db was never changed with the "first".

当你第一次调用some.sat第二次时,some_attribute_field的值已经等于“first”,activerecord知道这一点,所以它不会在db中更新到lock_version不会增加。第二次保存工作,因为db从未使用“first”进行更改。

Try changing the value in the second test to something other than "first" so that it is different to what is in the db.

尝试将第二个测试中的值更改为“first”以外的值,以使其与db中的值不同。

#2


I'm not a Ruby guy, but optimistic locking is familiar for me, so I'll try to help you to debug it.

我不是Ruby人,但乐观锁定对我来说很熟悉,所以我会尽力帮你调试它。

I presume the second save actually updates the database. If both objects have different lock_version AND lock_version is used in UPDATE, it's simply not possible (UPDATE would update zero rows). So, we have only two alternatives:

我假设第二个保存实际上更新了数据库。如果两个对象具有不同的lock_version并且在UPDATE中使用了lock_version,则根本不可能(UPDATE将更新零行)。所以,我们只有两种选择:

  • lock_version is not used in UPDATE statement or used incorrectly
  • lock_version未在UPDATE语句中使用或未正确使用

  • both objects got the same lock_version somehow
  • 两个对象以某种方式得到了相同的lock_version

(actually, there is a third alternative: both save() are in their own transaction, but I feel you have AUTOCOMMIT=true)

(实际上,还有第三种选择:save()都在他们自己的事务中,但我觉得你有AUTOCOMMIT = true)

Can you make actual SQL statements visible? The update statement should read something like

你能看到实际的SQL语句吗? update语句应该是这样的

... WHERE JOB_ID=123 AND LOCK_VERSION=8

When you'll have the actually queries at hand, it would be much easier to make sense of what's going on.

当你手头有实际的查询时,就可以更容易地了解正在发生的事情。

P.S. and one more: in your example in another topic you have this object:

附:还有一个:在你的另一个主题的例子中你有这个对象:

#<Job id: 323, lock: 8, worker_host: "second">

The save() call may be ignored by container if the properties are not changed comparing to load time. I don't know if ActiveRecord has this optimization or not though. But if it does, then second save() is ignored, and optimistic locking doesn't have a chance to kick in.

如果与加载时间相比未更改属性,则容器可以忽略save()调用。我不知道ActiveRecord是否有这种优化。但如果确实如此,则忽略第二个save(),并且乐观锁定没有机会启动。

#3


As Vladimir said, your test/example code is a bit flawed. First doesn't get stored in the datebase during the second save!() because no attributes have changed. See the following example:

正如弗拉基米尔所说,你的测试/示例代码有点缺陷。在第二次保存期间,首先不会存储在日期库中!()因为没有更改任何属性。请参阅以下示例:

foo = Account.create!

first = Account.find(foo.id)
first.cash = 100
first.save!


first = Account.find(foo.id)
first.cash = 100

puts "First lock before " + first.lock_version.to_s
first.save!
puts "First lock after " + first.lock_version.to_s

this produces:

% script/runner another_tester.rb                               
First lock before 1
First lock after 1

Using your example in the 2.3.2 version of rails works as it should with a Stale object exception when second is saved (both times!)

使用2.3.2版本的rails中的示例可以在保存第二个时使用Stale对象异常(两次都是!)