乐观锁与悲观锁
锁是一种保证数据安全的机制和手段,其并不是特定于某项技术的,其主要是通过在并发下控制多个操作的顺序执行,以此来保证数据安全地变动
例如在程序中,当多个线程修改共享变量时,可以给修改操作上锁(syncronized);在数据库中,当多个用户修改表中同一数据时,我们可以给该行数据上锁
悲观锁(Pessimistic Concurrency Control)
总是假设最坏的情况,每次取数据的时候都认为别人会修改,所以每次取数据都会加锁。这样别人在操作这条数据的时候,如果没有拿到锁,就会发生阻塞,操作就无法执行。
数据库中的行锁,表锁,读锁,写锁等都是悲观锁
乐观锁(Optimistic Concurrency Control)
总是假设最好的情况,每次取数据的时候都认为别人不会修改数据,所以不对数据加锁。但是会在更新的时候判断一下在此期间别人有没有更新这个数据,判断可以使用版本号机制和CAS算法实现。
乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中版本最为常用.
事务从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕需要提交至数据库时,会将之前取出的v1与数据的最新版本v2进行对比
-
v1 = v2:说明数据变动期间没有其他事务对该数据进行修改,此时允许事务对表中数据进行修改,且修改后version会加1 -
v1 != v2:说明有其他事务修改数据,此时不允许数据更新至表中,一般情况下是通知用户让其重新操作
锁的实现
实现场景
A和B用户都需要购买一本小说,两者打开了同一家书店,该店商品表goods结构和表中数据如下:
id | name | num |
|---|---|---|
1 |
小说 | 1 |
2 |
童话 | 1 |
可以看出,小说只有1本,如果A和B同时下单,在不加锁的情况下有可能导致超卖
悲观锁实现
解决思路
认为数据修改产生冲突的概率较大,所以在更新之前显式地对需要修改的记录加锁,直到修改完之后再释放锁
实现过程:
-
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字段,表变动之后结构如下:
id | name | num | version |
|---|---|---|---|
1 |
小说 | 1 |
0 |
2 |
童话 | 1 |
0 |
乐观锁认为数据修改产生冲突的概率不大,多个事务在修改数据之前先查出版本号,在修改时将版本号作为修改条件,所以只会有一个事务修改成功
实现过程:
-
A和B同时将小说的数据查出来,然后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,所以应该提醒用户重新处理
分析
优缺点
悲观锁
缺点:一个事务对数据进行加锁之后,其他事务需要一直等待,如果持有锁的事务执行时间过长,会显著影响系统的吞吐量
乐观锁
优点:不在数据库上进行加锁,只在更新时进行校验,能够避免悲观锁带来的吞吐量下降的问题
缺点:由于乐观锁是人为实现的,所以仅仅适用于自己的业务当中,如果有外来事务插入,可能发生错误
应用场景
悲观锁:适用于多写的应用类型,这样可以防止数据出错。

浙公网安备 33010602011771号