锁是一种保证数据安全的机制和手段,其并不是特定于某项技术的,其主要是通过在并发下控制多个操作的顺序执行,以此来保证数据安全地变动
例如在程序中,当多个线程修改共享变量时,可以给修改操作上锁(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
,所以应该提醒用户重新处理
分析
优缺点
悲观锁
缺点:一个事务对数据进行加锁之后,其他事务需要一直等待,如果持有锁的事务执行时间过长,会显著影响系统的吞吐量
乐观锁
优点:不在数据库上进行加锁,只在更新时进行校验,能够避免悲观锁带来的吞吐量下降的问题
缺点:由于乐观锁是人为实现的,所以仅仅适用于自己的业务当中,如果有外来事务插入,可能发生错误
应用场景
悲观锁:适用于多写的应用类型,这样可以防止数据出错。
来源:https://www.cnblogs.com/jeemzz/p/11432707.html