乐观锁与悲观锁-Python 实现

Python 乐观锁与悲观锁

参考:https://xie.infoq.cn/article/2085a95ad6b486d3905adc7d6

乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题

什么是乐观锁 ?

乐观锁在操作数据时采用乐观的态度,认为不会出现多个操作数据的事件。因此乐观锁不糊上锁,只是在进行更新操作的时候
判断该期间是否同时存在修改了数据的情况:若在修改时数据已被修改则放弃操作,否则继续执行操作。

乐观锁的实现

  1. CAS 机制。2.版本号机制。这里将采用 python + orm 的实现方式

CSA (Compare and Swap)

CSA 的操作主要包括了 3 个操作数:

  • 需要读写的内存位置(V)
  • 进行比较的预期值(A)
  • 拟写入的新值(B)
    具体的操作逻辑为:若内存位置的值等于预期的 A 值,则将该位置更新为新值 B, 否则不进行任何操作。
    直到重试次数消耗完毕。

    CSA 实现

    乐观锁(Optimistic Lock),适用于多读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from django.shortcuts import render
from django.http import JsonResponse
from django.views.generic import View
from django.db import transaction
from 应用名.models import GoodsSKU


# 类视图 (并发,乐观锁)
class Order(View):

@transaction.atomic
def post(self, request):
'''订单创建'''
count = 3 # 订购3件商品

# 设置事务保存点
s1 = transaction.savepoint()

# 乐观锁,最多尝试5次
for i in range(5):
# 查询商品的信息(库存)
try:
sku = GoodsSKU.objects.get(id=1)
except:
# 商品不存在
transaction.savepoint_rollback(s1)
return JsonResponse({'res': 1, 'errmsg': '商品不存在'})

# 判断商品的库存
if count > sku.stock:
transaction.savepoint_rollback(s1)
return JsonResponse({'res': 2, 'errmsg': '商品库存不足'})

# 更新商品的库存和销量
orgin_stock = sku.stock # 原库存 (数据库隔离级别必须是Read Committed;如果是Repeatable Read,那么多次尝试读取的原库存都是一样的,读不到其他线程提交更新后的数据。)
new_stock = orgin_stock - count # 更新后的库存
new_sales = sku.sales + count # 更新后的销量

# update 商品表 set stock=new_stock, sales=new_sales where id=1 and stock = orgin_stock
# 通过where子句中的条件判断库存是否进行了修改。(并发,乐观锁)
# 返回受影响的行数
res = GoodsSKU.objects.filter(id=1, stock=orgin_stock).update(stock=new_stock, sales=new_sales)
if res == 0: # 如果修改失败
if i == 4:
# 如果尝试5次都失败
transaction.savepoint_rollback(s1)
return JsonResponse({'res': 3, 'errmsg': '下单失败'})
continue # 再次尝试

# 否则更新成功
# 跳出尝试循环
break

# 提交事务
transaction.savepoint_commit(s1)

# 返回应答
return JsonResponse({'res': 4, 'message': '创建成功'})

什么是悲观锁 ?

悲观锁在操作数据的时候,比较悲观,认为别人会同时修改数据。
因此操作数据时会直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据

简单实现

例如,有用户 A 和用户 B,在同一家店铺去购买同一个商品,但是商品的可购买数量只有一个
direct

若,用户 A 和用户 B 同时下单,就会报错。
悲观锁的实现,往往依靠数据库提供的锁机制,在数据库中,我们如何用悲观锁去结局这个事情呢?

  1. 用户 A 在执行购买商品的时候,先尝试对该商品加上悲观锁。
  2. 若加锁失败,说明商品正在被其他事务进行修改,当前查询需要等待或者抛出异常,具体返回的方式需要开发者根据具体情况去定义
  3. 加锁成功:对商品进行修改
  4. 在此期间如果有其他用户对数据做修改或加锁的操作,都会等待我们解锁后或直接抛出异常
    direct

实际操作

那么如何加上悲观锁呢,1. 首先关闭 MySQL 数据库的自动提交属性。因为 MySQL 默认使用 autocommit 模式(默认提交)

1
set autocommit = 0;

悲观锁加锁 sql 语句:

1
select num form t_goods where id = 2 for update

此时,其他事务都不能获取到此行的数据去进行操作。

-------------THANKS FOR READING-------------