悲观锁与乐观锁
今天和大家一起了解一下悲观锁和乐观锁,在介绍这两个锁之前呢,先给大家介绍一下锁吧 众所周知 锁 在我们的生活中随处可见 用处都是来保护我们财产安全的
程序中也有锁,当多个线程修改共享变量时,我们可以给修改加上
(syncronized)
当多个用户修改表中同一数据时,我们可以给该行数据上锁(行锁)。因此,锁其实是在并发下控制多个操作的顺序执行,以此来保证数据安全的变动
并且,锁是一种保证数据安全机制和手段,而不是特定于某项技术的.悲观锁和乐观锁亦是如此
悲观并发控制又名悲观锁(Pessimistic Concurrency Control),第一眼看见它,相信每个人都会想到这是一个悲观的锁.
那这个悲观体现在什么地方呢?悲观是我们人类一种消极的情绪,对应到锁的悲观情绪,悲观锁认为被它保护的数据都是极其不安全的,每时每刻都有可能变动,一个事务拿到悲观锁后(可以理解为一个用户),其他任何事务都不能对数据进行修改,只能等待锁被释放才可以被释放
数据库中的行锁,表锁,读锁,写锁,以及syncronized实现的锁均为悲观锁.
再给大家说一下什么是数据库的行锁和表锁,mysql最常用的引擎是Innodb,Innodb默认使用的是行锁,而行锁是基于索引的,因此想要加上行锁必须命中索引,否则使用表锁
与悲观相对,乐观是我们人类一种积极的情绪,乐观并发控制又名乐观锁(Optimistic Concurrency Control)的乐观”乐观情绪”体现在:它认为数据的变动不会太频繁,因此,它允许多个事务同时对数据进行变动
但是,乐观不代表不负责,那么怎么去负责多个事务顺序对数据进行修改呢?
乐观锁通常是通过在表中增加一个版本(version)或(时间戳)来实现,其中版本最为常用
事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本v1与数据中最新的版本v2相对比,如果v1=v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,以此来表明数据已被变动。如果,v1不等于v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁是人为控制的。
根据上面了解的知识,来模拟一下需要加锁的场景,并利用悲观锁和乐观锁去解决
例如 A和B两个人想吃猪肉脯,于是他们打开了购物网站,并且找到了同一家猪肉脯的店铺,下面是这个店铺表goods结构和表中的数据
从表中可以看见只有一个猪肉脯了,在不加锁的情况下,如果A和B同时下单,就有可能超卖
所以先用悲观锁解决问题
思路是 我们认为数据修改改产生冲突的概率比较大,所以在更新之前,我们显示的对要修改的记录进行加锁,直到自己修改完再释放锁。加锁期间只有自己可以进行读写,其他事务只能读不能写。
A下单前先给猪肉脯这行数据(id=1)加上悲观锁(行锁),这时这行数据只能A进行操作,也就是只有A能购买,B想买就必须一直等带待,当A卖完,B想买的时候发现
数量为0 就会放弃购买
我们同事开启两个查询来演示一下
首先要在A中开启事务
Begin; begin work; start thransaction;三种来开启事务
然后就是加锁,这是加的行锁
select num from goods where id=1 for update;
同时也给B加上悲观锁
可以看到B一直在等待A释放锁,如果A长期不释放锁B会报错,有兴趣的可以试一下,
然后去在A中修改数据,让猪肉脯数量-1之后执行
Commit;或commit work
;结束事务,就可以看到猪肉脯数量变成0个了,在A结束事务时我们可以看到B中出现了什么
我们看到由于事务A释放了锁,事务B就结束了等待,拿到了锁,但是数据此时变成了0,那么B看到后就知道被买走了,就会放弃购买。然后就解决了问题
接下来就是乐观锁来解决问题了,乐观锁是通过版本号version来实现的,所以我创建了goods2并加上了version字段,结构如下
使用乐观锁的解决思路是,我们认为数据修改产生冲突的概率并不大,多个事务在修改数据的之前先查出版本号,在修改时把当前版本号作为修改条件,只会有一个事务可以修改成功,其他事务则会失败
A和B同时将猪肉脯(id=1下面都说是id=1)的数据查出来,然后A先买,A将id=1和version=0作为条件进行数据更新,即将数量-1,并且将版本号+1。
此时版本号变为1。A此时就完成了商品的购买。最后B开始买,B也将id=1和version=0作为条件进行数据更新,但是更新完后,发现更新的数据行数为0,此时就说明已经有人改动过数据,此时就应该提示用户重新查看最新数据购买
下面是乐观锁的加锁图解
我们也同样去进行演示
select num,version from goods2 where id=1 for update;
A和B同时获得了数据
然后A购买并更新了数据,可以查出更新后的数据
然后再让B进行购买更新数据
update goods2 set num =num-1,version =version-1 where id=1 and version=0;
查询数据
select num,version from goods2 where id=1
可以看出数据没有进行改变,此时就可以告诉客户重新处理
悲观锁
优点:悲观锁利用数据库中的锁机制来实现数据变化的顺序执行,这是最有效的办法
缺点:一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,那势必影响我们系统的吞吐量。
乐观锁
优点:乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势。
缺点:乐观锁因为是通过我们人为实现的,它仅仅适用于我们自己业务中,如果有外来事务插入,那么就可能发生错误。