乐观锁与悲观锁

一笑奈何 提交于 2020-03-11 08:25:14

(Lock)

锁是一种保证数据安全的机制和手段,其并不是特定于某项技术的,其主要是通过在并发下控制多个操作的顺序执行,以此来保证数据安全地变动

例如在程序中,当多个线程修改共享变量时,可以给修改操作上锁(syncronized);在数据库中,当多个用户修改表中同一数据时,我们可以给该行数据上锁

悲观锁(Pessimistic Concurrency Control)

总是假设最坏的情况,每次取数据的时候都认为别人会修改,所以每次取数据都会加锁。这样别人在操作这条数据的时候,如果没有拿到锁,就会发生阻塞,操作就无法执行。

数据库中的行锁,表锁,读锁,写锁等都是悲观锁

乐观锁(Optimistic Concurrency Control)

总是假设最好的情况,每次取数据的时候都认为别人不会修改数据,所以不对数据加锁。但是会在更新的时候判断一下在此期间别人有没有更新这个数据,判断可以使用版本号机制和CAS算法实现。

乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中版本最为常用.

事务从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕需要提交至数据库时,会将之前取出的v1与数据的最新版本v2进行对比

  • v1 = v2:说明数据变动期间没有其他事务对该数据进行修改,此时允许事务对表中数据进行修改,且修改后version会加1

  • v1 != v2:说明有其他事务修改数据,此时不允许数据更新至表中,一般情况下是通知用户让其重新操作

锁的实现

实现场景

AB用户都需要购买一本小说,两者打开了同一家书店,该店商品表goods结构和表中数据如下:

idnamenum
1 小说 1
2 童话 1

可以看出,小说只有1本,如果AB同时下单,在不加锁的情况下有可能导致超卖

悲观锁实现

解决思路

认为数据修改产生冲突的概率较大,所以在更新之前显式地对需要修改的记录加锁,直到修改完之后再释放锁

实现过程:

  • A下单时先给小说这条记录加锁,此时该行数据只能由A进行操作,B需要买的话需要等A操作结束

  • A买完之后,B查询发现数量已经为0,放弃购买

数据库演示

开启两个MySQL会话,即两个命令行,分别代表事务A和事务B

事务A事务B
BEGIN  
SELECT num FROM goods WHERE id=1 FOR UPDATE;  
num=1  
  BEGIN
  SELECT num FROM goods WHERE id=1 FOR UPDATE;
UPDATE goods SET num=num-1 WHERE id=1; waiting
COMMIT waiting
  num=0
  COMMIT

注意点:

  • 由于MySQL默认自动提交,所以此处显式地使用BEGIN开启事务

  • 使用FOR UPDATE给需要修改的数据加锁

  • 对于事务B来说,waiting状态表示在尝试获取数据的锁,由于数据的锁被事务A持有,所以此时事务B阻塞

  • 直到事务A释放锁资源之后,事务B才能获取数据,此时拿到的是事务A修改之后的数据

乐观锁实现

解决思路

乐观锁可以通过版本号机制来实现,所以需要给表goods加上version字段,表变动之后结构如下:

idnamenumversion
1 小说 1 0
2 童话 1 0

乐观锁认为数据修改产生冲突的概率不大,多个事务在修改数据之前先查出版本号,在修改时将版本号作为修改条件,所以只会有一个事务修改成功

实现过程:

  • AB同时将小说的数据查出来,然后A先买,以id=1 and version=0作为条件进行数据更新

  • A操作完成,小说数量减1,版本号加1

  • B开始购买,也将id=1 and version=0作为条件进行更新

  • B更新完之后发现更新的行数为0,说明已经有人更改过数据,此时提醒用户重新查看最新数据再进行购买

数据库演示

开启两个MySQL会话,即两个命令行,分别代表事务A和事务B

事务A事务B
SELECT num, version FROM goods WHERE id=1;  
num=1, version=0  
  SELECT num, version FROM goods WHERE id=1;
  num=1, version=0
UPDATE goods SET num=num-1, version=version+1 WHERE id=1 and version=0;  
SELECT num, version FROM goods WHERE id=1;  
num=0, version=1  
  UPDATE goods SET num=num-1, version=version+1 WHERE id=1 and version=0;
  SELECT num, version FROM goods WHERE id=1;
  num=0, version=1

对于事务B来说,可以看出更新的行数为0,所以应该提醒用户重新处理

分析

优缺点

悲观锁

优点:能够利用锁机制实现数据变化的顺序执行,保证数据安全性

缺点:一个事务对数据进行加锁之后,其他事务需要一直等待,如果持有锁的事务执行时间过长,会显著影响系统的吞吐量

乐观锁

优点:不在数据库上进行加锁,只在更新时进行校验,能够避免悲观锁带来的吞吐量下降的问题

缺点:由于乐观锁是人为实现的,所以仅仅适用于自己的业务当中,如果有外来事务插入,可能发生错误

应用场景

悲观锁:适用于多写的应用类型,这样可以防止数据出错。

乐观锁:适用于多读的应用类型,这样可以提高吞吐量。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!