- 锁,分为悲观锁(Pessimistic)和乐观锁(Optimistic).
- 对比的话,可以笼统的理解悲观锁是数据库层面,而乐观锁是应用层面。
一、悲观锁(Pessimistic)
- 特性
强烈的独占和排他。
它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。 - 实现
往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
一个典型的依赖数据库的悲观锁调用:
select * from account where name="Erica" for update;
这条 sql 语句锁定了 account 表中所有符合检索条件(name=“Erica”)的记录。 本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
注意:根据 name 是否有索引、是否是唯一索引、是否是主键,决定锁全表、区间、单个记录。
"select * from where sku xxx for update" 时,在 repeat read的隔离级别下,MySQL 加锁机制取决于sku的索引
> 如果 sku 没有索引,则锁全表。
> 如果 sku 有普通索引,则锁一个区间 - range lock。
> 如果 sku 是唯一索引,仅仅锁一行。
> 如果 sku 是主键,仅仅锁一行。
- 应用场景
一般对于资源的争用都可以使用悲观锁,比如电商系统中涉及到订单的部分,比如用户支付完成后可能会同时有多条支付成功的通知(做过支付的都知道一般有同步通知和异步通知),比如订单改价的同时可能用户正在支付等等,对于这种会对订单状态发生改变的操作,我们内部一般对这种操作都做加锁处理。 - Rails 的悲观锁
相关方法有:lock、lock! 和 with_lock.
lock 和 with_lock 都是封装 lock! 而来。
lock 相当于 lock! 的别名,但调用者可以是 relation 对象。
with_lock 和事务捆绑在一起,并且参数可以是代码块。
使用举例:
# 使用 lock,注意生成的 SQL
Account.lock.find(1) # 注意,这种最终会导致一个行锁
# SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` = 1 LIMIT 1 FOR UPDATE
# lock 结合 transaction 一起使用
Account.transaction do
# select * from accounts where name = 'shugo' limit 1 for update
shugo = Account.where("name = 'shugo'").lock(true).first # 注意,这里可不是行锁,这里会是一个表锁
yuko = Account.where("name = 'yuko'").lock(true).first
shugo.balance -= 100
shugo.save!
yuko.balance += 100
yuko.save!
end
- 如果查询的条件没有落在索引上,最好不要这样来用。折中一下,我们不愿意在处理一条数据时把整张表都锁住,但是又没有办法直接找到数据行的id,可以这么做:
# 使用 lock!
Account.transaction do
# select * from accounts where ...
accounts = Account.where(...)
account1 = accounts.detect {
|account| ... }
account2 = accounts.detect {
|account| ... }
# account1 和 account2 只能是单个对象
# select * from accounts where id=? for update
account1.lock!
account2.lock!
account1.