希望从实例到源代码,逐步解析乐观锁的工作流程,希望对你有所帮助。
# 添加lock_version列
Store.attribute_names # => ["id", "name", "number", "lock_version", "created_at", "updated_at"]
s1 = Store.first
s2 = Store.first
s1.number += 1
s.save # 正常
s2.number += 1
s2.save # 异常
-- sql日志
UPDATE `stores` SET `number` = 3, `updated_at` = '2019-05-11 01:15:19', `lock_version` = 1 WHERE `stores`.`id` = 1 AND `stores`.`lock_version` = 0
lock_version = 1
)lock_version = 0
)ActiveRecord::StaleObjectError
。代码位置
ActiveRecord::Locking::Optimistic
def _update_row(attribute_names, attempted_action = "update")
return super unless locking_enabled?
begin
# 获取乐观锁版本控制列,默认为lock_version
locking_column = self.class.locking_column
previous_lock_value = read_attribute_before_type_cast(locking_column)
# lock_version追加到更新字段中
attribute_names << locking_column
# lock_version + 1
self[locking_column] += 1
# 执行更新操作,返回受影响行数
# 更新字段:attribute_names + lock_version
# 查询条件:primary_key + lock_version
affected_rows = self.class._update_record(
attributes_with_values(attribute_names), # 更新字段
self.class.primary_key => id_in_database, # 主键查询条件
locking_column => previous_lock_value # 版本号查询条件
)
# 受影响行数不为1,则说明有并发,抛出异常
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
更新时检查当前读取记录 lock_version 是否为最新,来判断是否有并发的存在。
ActiveRecord::StaleObjectError