rails中乐观锁和悲观锁的使用
2018-12-18 本文已影响1人
小新是个程序媛
MySQL乐观锁和悲观锁的介绍可以参考之前的一篇文章
MySQL中的锁(行锁,表锁,乐观锁,悲观锁,共享锁,排他锁)
同时补充下,在悲观锁中,对于如下语句的加锁机制,解释如下
"select * from where xxx for update"
在 repeat read的隔离级别下,MySQL 加锁机制取决于name的索引
如果name没有索引,则锁全表。
如果name 有普通索引,则锁一个区间 - range lock。
如果 name 是唯一索引,仅仅锁一行。
如果name 是主键,仅仅锁一行。
今天主要介绍一下锁在rails中的使用
1. 悲观锁的使用
# select * from accounts where id=1 for update
Account.lock.find(1)
# 注意,这种最终会导致一个行锁
# select * from accounts where name = 'shugo' limit 1 for update
Account.where("name = 'shugo'").lock(true).first
# 注意,这里可能不是行锁,行锁 表锁取决于name字段的属性
Rails 也提供了一个很方便的方法 with_lock'
来锁住单个记录,并且内嵌在事务之中。下面代码中的两段是等价的:
account = Account.find(1)
Account.transaction do
account.lock!
account.balance -= 100
account.save!
end
# 和下面是等价的
account.with_lock do
account.balance -= 100
account.save!
end
2. 乐观锁的使用
使用乐观锁之前需要给数据库增加一列 lock_version字段给相应的表,Rails 会自动识别这一列,像数据库提交数据的时候自动带上,如果事务提交失败,那么 Rails 会抛一个 ActiveRecord::StaleObjectError 的异常。
另外,乐观锁是默认打开的,如果要关闭,需要配置一下。
在大鱼系统中,库存管理是使用乐观锁的,流量没那么大,不太可能多个用户同时预订同一个住宿的同一个间夜,概率比较小,所以目前是使用乐观锁来实现的。如果抛异常,那么还可以进行重试。
比如,下面这段代码会进行重试:
retry_times = 3
begin
@order.with_lock do
@order.set_paid!
end
rescue ActiveRecord::StaleObjectError => e
retry_times -= 1
if retry_times > 0
retry
else
raise e
end
rescue => e
raise e
end
需要注意的地方
- 一般,使用锁的时候和事务同时使用,所以 with_lock 是用的比较多的,而且尽量使用行锁而不是表锁。
- 另外,也注意异常的处理,需要使用那些会抛异常的方法;
- 对于乐观锁,还需要注意如果是前端操作频繁,那么还需要把 lock_version 写入到 form 表单中,否则起不到锁的作用,这里讲的很详细了
参考:https://ruby-china.org/topics/28963
附录:使用乐观锁的示例:
terminal
rails g migration add_lock_version_to_products lock_version:integer
rake db:migrate
products/_form.html.erb
<%= f.hidden_field :lock_version %>
products_controller.rb
class ProductionsController < ApplicationController
def update
# ... update code
rescue ActiveRecord::StaleObjectError
@production.reload.attributes = params[:production].reject do |attrb, value|
attrb.to_sym == :lock_version
end
flash.now[:error] = "Another user has made a change to that record "+
"since you accessed the edit form."
render :edit, :status => :conflict
end
end