悲观锁,乐观锁

一. 悲观锁

1. 原生mysql悲观锁

# 开启事务
begin;

# 行锁,如果不加锁,可能会出现卖超了
select * from book where id = 1 for update;

# order表中加数据---生成订单
insert into order。。。。。

# 扣减库存, 更新。
update book set count = count - 1 where id = 1;

# 提交事务
commit;

2. ORM 实现悲观锁(select_for_update())

使用悲观锁实现下单

# 整个过程在一个事物中---》改两个表:book表减库存,订单表生成记录
@transaction.atomic
def seckill(request):
    # 锁住查询到的book对象,直到事务结束
    sid = transaction.savepoint() # 保存点

    # 悲观锁: select_for_update()
    # 加锁了--> 行锁还是表锁? 分情况,都有可能

    # 加悲观锁,行锁,锁住当前行
    book = Book.objects.select_for_update().filter(pk=1).first()
    if book.count > 0:
        print('库存可以,下单')
        # 订单表插入一条
        Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单')
        # 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
        time.sleep(random.randint(1, 4))  # 模拟延迟
        book.count=book.count-1
        book.save()
        transaction.savepoint_commit(sid)  # 提交,释放行锁
        return HttpResponse('秒杀成功')
    else:
        transaction.savepoint_rollback(sid) #回滚,释放行锁
        return HttpResponse('库存不足,秒杀失败')

二. 乐观锁(秒杀)

1. 普通版

因为乐观锁是通过特殊标识(版本)进行判断,当并发时,多个线程可能同时读取版本,此时版本是一样的,但是修改时再次判断版本时,发现版本被修改,就会秒杀失败,这样库存就不会被全部秒杀完, 因此普通版是有缺陷的。

@transaction.atomic
def seckill(request):
    # 锁住查询到的book对象,直到事务结束
    sid = transaction.savepoint()
    book = Book.objects.filter(pk=1).first()  # 没加锁
    count = book.count
    print('现在的库存为:%s' % count)
    if book.count > 0:
        print('库存可以,下单')
        Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单-乐观锁')
        # 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
        # time.sleep(random.randint(1, 4))  # 模拟延迟
        res = Book.objects.filter(pk=1, count=count).update(count=count - 1)
        if res >= 1:  # 表示修改成功
            transaction.savepoint_commit(sid)
            return HttpResponse('秒杀成功')
        else:  # 修改不成功,回滚
            transaction.savepoint_rollback(sid)
            return HttpResponse('被别人改了,回滚,秒杀失败')

    else:
        transaction.savepoint_rollback(sid)
        return HttpResponse('库存不足,秒杀失败')

2. 完整版

完整版是通过判断库存还有没有,让秒杀失败的通过 “while循环” 继续进行秒杀,直到库存不足,还没成功的就是真正失败。具体代码如下:

from django.http import HttpResponse
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db import transaction
from seckill.get_result import get_result
from .models import Goods, Order
import datetime


class SeckillGoodsView(ViewSet):
    @action(methods=['GET'], detail=False)
    # 2 乐观锁秒杀--普通版
    @transaction.atomic
    def seckill(self, request):
        while True:
            # 锁住查询到的book对象,直到事务结束
            sid = transaction.savepoint()
            goods = Goods.objects.filter(pk=1).first()  # 没加锁
            count = goods.store
            print('现在的库存为:%s' % count)
            if goods.store > 0:
                print('库存可以,下单')
                Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单-乐观锁')
                # 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
                # time.sleep(random.randint(1, 4))  # 模拟延迟
                res = Goods.objects.filter(pk=1, store=count).update(store=count - 1)
                if res >= 1:  # 表示修改成功
                    transaction.savepoint_commit(sid)
                    print('秒杀成功')
                    return HttpResponse('秒杀成功')
                else:  # 修改不成功,回滚
                    transaction.savepoint_rollback(sid)
                    print('秒杀失败')
                    continue

            else:
                transaction.savepoint_rollback(sid)
                return HttpResponse('库存不足,秒杀失败')

三. 异步(乐观锁)秒杀

效率最高的, 部分代码展示如下:
完整代码:https://gitee.com/codegjj/seckill

# -------------------------- 视图 views.py ---------------------------------

from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
from seckill.get_result import get_result
from .tasks import seckill_goods


class SeckillGoodsView(ViewSet):
    @action(methods=['GET'], detail=False)
    # 2 乐观锁秒杀--普通版
    # @transaction.atomic
    def seckill(self, request):
        task_id = seckill_goods.delay()
        print(task_id)
        return Response({'task_id': str(task_id)})

    # 获取结果
    @action(methods=['GET'], detail=False)
    def result(self, request):
        task_id = request.query_params.get('task_id')
        res = get_result(task_id)

        return Response({'result': res})


# -------------------------- 任务 tasks.py ---------------------------------
from celery import shared_task
from .models import Goods, Order
from django.db import transaction
import datetime


@shared_task()
@transaction.atomic
def seckill_goods():
    while True:
        # 锁住查询到的book对象,直到事务结束
        sid = transaction.savepoint()
        goods = Goods.objects.filter(pk=1).first()  # 没加锁
        count = goods.store
        print('现在的库存为:%s' % count)
        if goods.store > 0:
            print('库存可以,下单')
            Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单-乐观锁')
            # 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
            # time.sleep(random.randint(1, 4))  # 模拟延迟
            res = Goods.objects.filter(pk=1, store=count).update(store=count - 1)
            if res >= 1:  # 表示修改成功
                transaction.savepoint_commit(sid)
                print('秒杀成功')
                return '秒杀成功'
            else:  # 修改不成功,回滚
                transaction.savepoint_rollback(sid)
                print('秒杀失败')
                continue

        else:
            transaction.savepoint_rollback(sid)
            print('123')
            return '库存不足,秒杀失败'

四. reids 实现乐观锁

posted @ 2024-06-27 21:18  codegjj  阅读(36)  评论(0)    收藏  举报