文章目录
什么是并发?
前情提要:
- 上节事务解决的问题是,多个逻辑任务之间是 必须同时成功,或者同时失败的(比如创建订单对象与创建订单商品对象)
并发场景
- 当库存有限,但是让同事有多个用户购买,会发生并发问题。
- 计入商品a有 1件商品, a,b用户同时购买,读取数据库中的商品数量时都是1 ! ,结果二人都创建订单成功,但是:数据库中很明显只有一件商品,不够卖给两个人。这就是接着要解决的并发问题。
解决并发的方式:
(1) 排队
- 本质上还是一个一个的处理请求,但是不用让用户去感受到他在等别人执行完之后,才去执行他的,需要使用到 celery。 开启只有一个进程的celery,一个订单一个订单请求的在后台排队处理。
(2) 利用 **‘锁’**的方案。
悲观锁
- 解释:这个锁是真实存在的,在mysql数据库当中,为指定的那个数据加锁,学过操作系统的话知道,相当于是一种临界资源。类似于互斥锁, 但是弊端就是处理不好很容易产生死锁!
select stock from tb_sku where id=1 for update;
SKU.objects.select_for_update().get(id=1)
乐观锁
- 解释: 乐观锁并不是真实存在的锁,而是在更新的时候判断此时的库存是否是之前查询出的库存,如果相同,表示没人修改,可以更新库存,否则表示别人抢过资源,不再执行库存更新
update tb_sku set stock=2 where id=1 and stock=7;
SKU.objects.filter(id=1, stock=7).update(stock=2)
mysql事务隔离级别的问题
- 问题引入: 上面的乐观锁已经可以很好地处理我们的业务需要了,但是有一点,他是放在一个事务里面。 MySQL默认的 事务隔离级别是 可重复读,也就是说,不管其他用户如何购买商品,影响不到 我们这个事务中对数据的判断,因为事务读取数据不会 更新,加入别人事务完成买走了商品还有0件,我们乐观锁读取到的还是之前的1,不会去 刷新 读取到新数据。
- 解决: 更改数据库的 事务级别。
mysql事务级别
Serializable:
- 串行化,一个事务一个事务的执行
- 执行效率太低了,意思就是只允许一个事务执行,执行完毕才允许其他事务执行。
Repeatable read(默认):
- 可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响
Read committed:
- 读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值
Read uncommitted:
- 读取未提交,其他事务只要修改了数据,即使未提交,本事务也能看到修改后的数据值
使用乐观锁的时候,如果一个事务修改了库存并提交了事务,那其他的事务应该可以读取到修改后的数据值,所以不能使用可重复读的隔离级别,应该修改为读取已提交(Read committed)
修改事务级别方式(ubuntu):
现在是时候上我们的处理业务代码了
with transaction.atomic(): # 禁止事务自动提交
# 开启事务
sid = transaction.savepoint()
# 2.创建订单基本对象
order_id = '%s%09d' % (now.strftime('%Y%m%d%H%M%S'), user.id)
total_count = 0
total_amount = 0
if pay_method == '1':
# 待发货
status = 1
else:
# 待支付
status = 2
order = OrderInfo.objects.create(
order_id=order_id,
user_id=user.id,
address_id=address_id,
total_count=0,
total_amount=0,
freight=10,
pay_method=pay_method,
status=status
)
# 3.查询商品对象
skus = SKU.objects.filter(pk__in=cart_selected_int)
# 4.遍历
for sku in skus:
cart_count = cart_dict_int.get(sku.id)
# 4.1判断库存,不足则提示,如果足够则继续执行
if sku.stock < cart_count:
# 回滚事务
transaction.savepoint_rollback(sid)
return http.JsonResponse({
'code': RETCODE.PARAMERR, 'errmsg': '商品[%d]库存不足' % sku.id})
# 4.2修改sku的库存、销量
# sku.stock -= cart_count
# sku.sales += cart_count
# sku.save()
# 4.2 使用乐观锁
stock_old = sku.stock
stock_new = sku.stock - cart_count
sales_new = sku.sales + cart_count
result = SKU.objects.filter(pk=sku.id, stock=stock_old).update(stock=stock_new, sales=sales_new)
# result表示sql语句修改数据的个数
if result == 0:
# 库存发生变化,未成功购买
transaction.savepoint_rollback(sid)
return http.JsonResponse({
'code': RETCODE.PARAMERR, 'errmsg': '服务器忙,请稍候重试'})
# 4.3创建订单商品对象
order_sku = OrderGoods.objects.create(
order_id=order_id,
sku_id=sku.id,
count=cart_count,
price=sku.price
)
# 4.4 计算总金额、总数量
total_count += cart_count
total_amount += sku.price * cart_count
# 5.修改订单对象的总金额、总数量
order.total_count = total_count
order.total_amount = total_amount + 10
order.save()
transaction.savepoint_commit(sid)
# 6.删除购物车中选中的商品
redis_cli.hdel('cart%d' % request.user.id, *cart_selected_int)
redis_cli.srem('selected%d' % request.user.id, *cart_selected_int)
好的,基本上就处理完成了!