我在前端有一个表格,里面有多行可以读取和编辑。每行都有一个编辑图标,用户将单击该图标,并且会弹出一个对话框来更新行中的字段。对话框中的保存按钮将保存字段(调用更新 API(,关闭对话框,然后通过调用具有相同页面、筛选器和排序顺序的列表 API 来重新加载表。
为了支持多个用户读取和编辑同一个表,我想锁定单击编辑图标的行,并在用户单击弹出的对话框中单击保存或取消时解锁。为此,我向数据库中的每个row
添加了一个lock
字段。
当用户单击编辑图标时,我发送lock
API 调用:
lock_success = false
message = nil
row = Row.find(id)
# (with_lock)
if row.lock.nil?
row.lock = @current_user.user_name
row.save!
lock_success = true
end
# (with_lock_end)
当编辑对话框在保存或取消时关闭时:
Row.update(id, lock: nil)
但是,可能会有这种情况吗?
(1) row = Row.find(1)
(2) row = Row.find(1)
(1) if row.lock.nil?
(2) if row.lock.nil?
(1) row.lock = @current.user_name
(2) row.lock = @current.user_name
(1) row.save!
(2) row.save!
如果我row.with_lock
包裹在(with_lock)
和(with_lock_end)
上,它应该可以解决这个问题吧?
最后,我可以对lock_version
使用乐观锁定吗?
- 用户 (1( 使用版本 1 加载第 1 行。
- 用户 (2( 使用版本 1 加载第 1 行。
- 用户 (1( 使用版本 1 更新第 1 行,现在第 1 行是版本 2。
- 用户 (2( 使用版本 1 更新第 1 行,获取过时对象异常。
然后我就不需要用with_lock
包装更新调用了?但是,如何通过此方法跟踪谁锁定了该行?
尝试做这样的事情:
lock_obtained = Row.where(id: 1, locked_by: nil).update_all(locked_by: @current_user.user_name, locked_at: Time.zone.now) == 1
# this try to update the row with ID 1 with the current_user's name only if it's not already locked, if the call returns 1 it means that the record was updated so @current_user locked it, if it returns 0 it means the record was already locked by someone else, that's the "== 1" for
现在,您将尝试锁定该行,并知道该行之前是否已锁定或仅在一行中成功,这样您就不会遇到该争用条件。
只是一些建议: - 我会使用带有用户 ID 的整数作为locked_by列而不是名称 - 我也会保存locked_at时间戳,听起来可以使用完整 - 如果用户关闭锁定行的选项卡,该怎么办?对于这类东西,你应该使用ActionCable,这样你就可以检测用户是否也断开连接了。