1818 words, 6 mins
rails 中关于乐观锁的使用非常简单,可以查看文档
使用
只需要在数据表添加lock_version
这个字段就可以了。原理就是每一次update
操作都会递增lock_version
,如果有多个update
同时对同一个lock_version
的行进行更新,只要前一个更新成功,后一个再去更新就会抛异常StaleObjectError
。
class AddLockingColumns < ActiveRecord::Migration
def self.up
add_column :destinations, :lock_version, :integer
end
def self.down
remove_column :destinations, :lock_version
end
end
可以指定锁定版本的字段名称
class Destination
self.locking_column = "my_custom_locking"
end
例子:
p1 = Person.find(1)
p2 = Person.find(1)
p1.first_name = "Michael"
p1.save
p2.first_name = "should fail"
p2.save # Raises an ActiveRecord::StaleObjectError
删除的时候也会检查
p1 = Person.find(1)
p2 = Person.find(1)
p1.first_name = "Michael"
p1.save
p2.destroy # Raises an ActiveRecord::StaleObjectError
结合 web 前端,最好是在form
表单提交的时候添加一个hidden field
,名字就是lock_version
,像这样
<%= form_for @destination do |form| %>
<%= form.hidden_field :lock_version %>
<%# ... other inputs %>
<% end %>
原理
更新语句构造的 sql会带着lock_version
UPDATE `orders` SET `leave_count` = 9, `updated_at` = '2018-07-15 06:47:28', `lock_version` = 1 WHERE `orders`.`id` = 1 AND `orders`.`lock_version` = 0
一旦lock_version
已经被修改,更新语句影响的数据行就为 0,查看源码实现也可以验证:
# active_record/locking/optimistic.rb
def _update_row(attribute_names, attempted_action = "update")
return super unless locking_enabled?
begin
locking_column = self.class.locking_column
# 先获取 lock_version 的值
previous_lock_value = read_attribute_before_type_cast(locking_column)
attribute_names << locking_column
self[locking_column] += 1
# 带着 lock_version 去 update
affected_rows = self.class._update_record(
attributes_with_values(attribute_names),
self.class.primary_key => id_in_database,
locking_column => previous_lock_value
)
# 如果没 update 成功,也就是 affrected_rows == 0,则说明被别的并发修改抢占了,于是抛出异常
if affected_rows != 1
raise ActiveRecord::StaleObjectError.new(self, attempted_action)
end
affected_rows
# If something went wrong, revert the locking_column value.
rescue Exception
self[locking_column] = previous_lock_value.to_i
raise
end
end
PREVIOUSLinux性能学习第五周-网络性能优化
NEXTpostgresql字符类型使用