Rails 乐观锁这次应该说清楚了吧

flydragon · 2019年05月11日 · 最后由 hwang 回复于 2020年10月13日 · 1351 次阅读

概要

希望从实例到源代码,逐步解析乐观锁的工作流程,希望对你有所帮助。

实例

# 添加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

实例分析

  1. 递增 lock_version 追加到更新字段后面 (lock_version = 1)
  2. 当前 lock_version 合并到更新条件后面 (lock_version = 0)
  3. 执行更新 sql,返回更新行数,理论上如果没有并发的话,受影响行数一定为 1。
    • 更新行数为 1,说明当前版本号最新,期间无并发操作。
    • 更新行数不为 1,说明当前版本号失效,有并发操作,抛出异常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 是否为最新,来判断是否有并发的存在。

  1. 乐观锁通过版本号来检测并发
  2. 检测的条件是,在当前记录版本号下执行更新受影响的行数是否为 1
  3. 出现并发抛出异常ActiveRecord::StaleObjectError
  4. 并发处理,需要手动处理(捕获异常-》并发处理)
  5. 处理方法包括重试,具体可参考Rails 中乐观锁与悲观锁的使用
flydragon 关闭了讨论。 05月11日 20:48
flydragon 重新开启了讨论。 05月11日 20:48
需要 登录 后方可回复, 如果你还没有账号请 注册新账号