悲观锁、乐观锁的区别和实现

通过阅读本篇文章,将为您解决以下四个疑问。

1、何为乐观锁?
2、何为悲观锁?
3、乐观锁如何实现?
4、悲观锁如何实现?

乐观锁

乐观锁对数据的更改持乐观态度,认为临界区不会发生冲突,持有数据更改的版本号,只有数据在提交的时候校验版本号,如果冲突则回滚事务,不冲突则提交数据。

乐观锁并不是锁,而是一种概念。

乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,
这样可以省去了锁的开销,加大了系统的整个吞吐量。

乐观锁的实现

乐观锁进入临界区时记录版本号,在提交时比对数据库版本实现。

例子,比如一个用户支付了订单,后台人员操作设置订单状态为已发货,但是同时用户取消了订单,导致状态撞车,怎么解决?

mysql数据库中表中都有ver版本号的字段,数据更新了自增这个字段,在数据提交的时候校验, 如果ver一直没变就更新数据,自增ver; 如果变了则放弃更新。基于数据库的乐观锁实现如下:

select ver,status from orders where id=1;
update orders set status = "shipped",ver = ver + 1 where id = 1 and ver = :ver;
如果ver没有被别的线程改过,则更新成功了;
如果更改过,ver自增了,则更新失败了。

悲观锁

顾名思义,对数据的更改持悲观态度,认为每次都会并发执行造成冲突,每次都会上锁保护临界区,别人想拿这个数据就会block直到它拿到锁。这样的锁称为悲观锁。
数据库中的表锁,行锁,排它锁属于悲观锁,大部分用的锁是悲观锁。

悲观锁适合冲突比较严重的情况下即多个客户同时修改一条数据。

悲观锁好处:保证不会发生并发执行,数据操作效率高。

悲观锁坏处:影响并发量,可能产生死锁。

悲观锁的实现

例子,比如一个用户支付了订单,后台人员操作设置订单状态为已发货,但是同时用户取消了订单,导致状态撞车,怎么解决?

还是上一个例子,悲观锁的话,可以利用数据库for update去实现

start transaction; //开启事务
select * from orders where id = 1 for update;
//select末尾for update表示在此次事务提交完成之前select查询到的语句被锁定,在事务结束之前不会被改变
//由于InnoDB预设是Row-Level Lock,所以只有明确的指定主键
//MySQL才会执行Row lock (只锁住被选取的资料例) ,
//否则MySQL将会执行Table Lock (将整个资料表单给锁住)
update orders set status = "shipped" where id = 1;
commit; //提交事务

我们仍然可以在程序级别的分布式锁去实现

$this->getLock($id, 5) //用订单的id作为key去获取锁
$this->updateOrderStatus($id, $toStatus); //进行更新操作,必须有状态机,判断前置状态才能更新,否则失败。
$this->releaseLock //释放锁

这种方式的话,依然有缺陷。更新的时候必须判断前置状态在更新,比如已经取消,就不能发货了等逻辑。

另外解决方式,更新数据是带上更新前的“状态”

update orders set status = "shipped" where id = 1 and status = "paid";

数据库的更新会加锁,这样不会出现查出来是老数据的情况。

你可能感兴趣的:(服务器技术,悲观锁,乐观锁)