java中的乐观锁和悲观锁

有一个购买行为事务,需要更新数据库,通常的sql语句是:

update item set amount = amount - 1 where item_id = 1;

然而当amount只有1个的时候,同时有两个顾客进入了事务进行购买行为会如何,最后amount=-1,两个顾客都获得了这个商品,这显然不合理,那么该如何解决这一问题呢?

网上查了一些方法,发现了两个我认为比较不错的方法:乐观锁和悲观锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

下面来简单介绍一下他们的执行原理。

1. 乐观锁

1) 概念: 在执行修改操作时不判断是否存在冲突,而是到了操作完成后再判断是否存在冲突,如有冲突则回滚

2) 适用情况: 一般适用于回滚代价低,且冲突较少的情况.

3) 优点: 执行操作时不会造成阻塞

4) 缺点: 如果冲突较多,将造成较多的回滚操作

5) 实现: 一般使用版本控制的方式实现

6) 实现例子:

begin;

select @cur_amount := amount from item where item_id = 1;

update item set amount = amount - 1 where amount = @cur_amount and item_id = 1;

commit;

// 最后根据对数据更新行数是否为1来告诉用户是否购买成功。

这是使用乐观锁来更新的例子, @cur_amount是本次事务A获得的数量, 例如为 1, 而另外一个事务B假如于事务A之前执行完了跟新操作, 那么此时数据库中的amount将变为0.

那么事务A的 update 中语句 amount = @cur_amount 将是 0 = 1 而不成立, 所以此次购买会失败

我这里只是一个简单的例子,实际操作中,可能这个事务中间会进行大量操作,而最后会因为amount != @cur_amount 而回滚

 

2. 悲观锁

1) 概念: 在执行操作前就进行是否需要锁的判断,若有操作正在执行,则需要等待锁释放。

2) 适用情况: 冲突较多的情况

3) 优点: 不需要进行回滚

4) 缺点: 需要串行执行, 会造成阻塞

5) 实现: 使用排他锁

6) 实现例子:

begin;

select amount from item where item_id = 1 for update;

// 通过amount来做出一些行为,例如告诉用户库存不足,购买失败,然后只有amount > 1才进入更新库存操作

update item set amount = amount - 1 where item_id = 1;

commit;

由于是串行执行,其他事务的for update必须等该当前事务的for update语句执行,所以我们不必担心我们获得的amount被修改过,因为它永远是最新的

事实上,对于这种购买行为的最佳解决方案是

update item set amount = amount - 1 where item_id = 1 and amount > 0;

由于update操作在repeatable-read中是串行执行的,所以我们大可以不加锁,直接这么一句就解决了,当然这是一种特例。

猜你喜欢

转载自blog.csdn.net/i_hanjt/article/details/78970394