悲观锁

使用方式

悲观并发控制在维基百科上是这样介绍的:

悲观并发控制,又名悲观锁,是一种并发控制方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其它事务才能够执行与该锁冲突的操作。 悲观锁假定会发生并发冲突,悲观锁的实现往往依靠数据库提供的锁机制。

在数据库中,悲观锁的流程如下:

  • 在对任意记录修改之前,先尝试为该记录加上排它锁;
  • 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。
  • 如果成功加锁,那么就可以对记录做修改,事务完成后就可以解锁。其间如果有其它对该记录做修改或加排它锁的操作,都会等待解锁或直接抛出异常。

举例来讲,在一个事务中,如果对contract表中某行记录更新,首先执行执行下面这条sql:
SELECT * FROM CONTRACT where id = ** FOR UPDATE
这样就会对该行记录加上X锁,如果加锁失败则意味着该行记录被正在被其它事务进行写操作。上面讲到的加锁存在一个问题,如果另外一个事务尝试对已经加锁的记录再次尝试加锁,那么会一直等待,直到超时。Oracle(或者OceanBase)对此进行了优化,支持SELECT … FOR UPDATE [WAIT n|NOWAIT]。WAIT n指如果等待n秒,如果还获取不到该记录的x锁,那么直接返回,NOWAIT表示不等待,立即返回结果。
需要注意的是,SELECT … FOR UPDATE会对所有扫描过的行加锁,因此在使用悲观锁要走索引(MySql)。另外使用SELECT ** FOR UPDATE加上X锁后,仍然可以使用SELECT from contract where id =…查询出同一行记录。

优点与不足

悲观并发控制采用了“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是,数据库通过锁管理器实现加锁或者解锁会增加额外开销,降低数据库的并行性。

乐观锁

使用方式

乐观锁在维基百科上是这样介绍的:

乐观并发控制,又名乐观锁,是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其它事务又修改了该数据。如果其它事务有更新的话,正在提交的事务会进行回滚。

乐观锁认为数据更新一般不会造成冲突,所以在数据进行提交更新时,才会真正的对数据冲突与否进行检测,如果出现冲突,数据库进行回滚操作。
相比于悲观锁,在对数据库进行更新时,乐观锁并不会使用数据库提供的锁机制,而是在业务层进行控制。一般实现乐观锁的方式就是记录版本,在记录版本时通常有两种做法:

  • 使用版本号
  • 使用时间戳

下面的例子中使用了时间戳表示版本

```
1、查询出合同信息
select (contract_id,status,version….) from contract
2、生成一个时间戳
newVersion = new Date();
3、更新合同状态
update contract set status = 3,version=newVersion
where contract_id=#{id} and version=#{version}
除此之外,还可以使用表中有意义的字段,比如商品库存的数量。

<br />update prouduct_stock set stockCount = stockCount - deductCount<br />where stockCount - deductCount > 0<br />

优点与不足:

在事务并发度小的场景中,有比较好的效果。但是如果一个用户查询数据在做更新操作之前,第二个用户查询同一行记录,第一个用户更新并提交后,第二个用户不得不重新从数据库取数据。

参考文献

[1]深入理解乐观锁与悲观锁
[2]mysql悲观锁总结和实践