为啥activerecord乐观锁定每行只工作一次?

Posted

技术标签:

【中文标题】为啥activerecord乐观锁定每行只工作一次?【英文标题】:Why does activerecord optimistic locking work only once per row?为什么activerecord乐观锁定每行只工作一次? 【发布时间】:2010-10-23 05:57:16 【问题描述】:

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

我之前的问题是关于同样的问题,但我现在可以缩小范围:

我整天都在玩这个,试图弄明白它。我有一个带有 lock_version 列的表,指定如下:

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

我会这样做:

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

然后我验证 first 和 second 引用同一个对象 - 它们的 id 相同,并且我使用 mysql 命令行工具在数据库中看到该行。

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

到目前为止没有问题。我正确地得到了一个 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

...什么也没有发生。事实证明,我唯一一次得到正确(抛出异常)行为是当 first 和 second 的 lock_version 为 0 时。但是,在第一次保存之后,它不再是 0。这到底是怎么回事?

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

谢谢...

【问题讨论】:

【参考方案1】:

正如 Vladimir 所说,您的测试/示例代码有点缺陷。在第二次保存期间,First 不会存储在数据库中!() 因为没有属性更改。请参阅以下示例:

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

这会产生:

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

在 2.3.2 版本的 rails 中使用您的示例,在保存秒时(两次都!)

【讨论】:

我刚刚用 2.2.2 尝试了同样的测试,它工作正常(两次都是过时的对象异常)【参考方案2】:

当您第二次调用 first.save 时, some_attribute_field 的值已经等于“first”,activerecord 知道这一点,因此它不会在数据库中更新到 lock_version 不会增加。第二次保存有效,因为数据库从未被“第一次”更改。

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

【讨论】:

您可能想在 API 中查找“脏”和“更改”(我使用的是 gotapi.com)。它什么都不做,因为没有什么 to 做的。【参考方案3】:

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

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

lock_version 未在 UPDATE 语句中使用或使用不正确 两个对象不知何故获得了相同的 lock_version

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

你能让实际的 SQL 语句可见吗?更新语句应该是这样的

... WHERE JOB_ID=123 AND LOCK_VERSION=8

当您手头有实际的查询时,就会更容易理解正在发生的事情。

附:还有一个:在另一个主题的示例中,您有这个对象:

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

如果属性与加载时间相比没有改变,容器可能会忽略 save() 调用。我不知道 ActiveRecord 是否有这种优化。但如果是这样,那么第二个 save() 将被忽略,并且乐观锁定没有机会启动。

【讨论】:

以上是关于为啥activerecord乐观锁定每行只工作一次?的主要内容,如果未能解决你的问题,请参考以下文章

休眠乐观锁定..它是如何工作的?

Rails - 乐观锁定总是触发 StaleObjectError 异常

如何在休眠中进行乐观锁定

RESTful 应用程序中的乐观锁定

为啥 NSTimer 在 swift 中只工作一次

php并发控制 , 乐观锁