Django补充 悲观锁乐观锁事务秒杀

1 django中实现事务的几种方式

  • Django是支持事务操作的,它的默认事务行为是自动提交,
  • 具体表现形式为:每次数据库操作(比如调用save()方法)会立即被提交到数据库中。
  • 但是如果你希望把连续的SQL操作包裹在一个事务里,就需要手动开启事务
from django.db import models

# Create your models here.
class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()  #
    count = models.SmallIntegerField(verbose_name='库存')
class Order(models.Model):
    order_id = models.CharField(max_length=64)
    order_name = models.CharField(max_length=32)
# https://zhuanlan.zhihu.com/p/622987268


# 根据粒度不同,三种
######## 全局##########
	-全局,每次请求在一个事务中,粒度太大,事务时间很长
    DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.mysql',
         'NAME': 'lqz',
         'HOST': '127.0.0.1',
         'PORT': '3306',
         'USER': 'lqz',
         'PASSWORD': 'lqz123',
          #全局开启事务,绑定的是http请求响应整个过程
         'ATOMIC_REQUESTS': True, 
         }
    }
# 局部禁用全局事务
from django.db import transaction
# 局部禁用事务
@transaction.non_atomic_requests
def seckill(request):
    return HttpResponse('秒杀成功')
 
# 多数据库,用default的视图不受事务控制
@transaction.non_atomic_requests(using='default')
def seckill(request):
    return HttpResponse('秒杀成功')



####### 视图开启事务##############
# fbv开启
from django.db import transaction
@transaction.atomic
def seckill(request):
    return HttpResponse('秒杀成功')


# cbv开启
from django.db import transaction
from rest_framework.views import APIView
class SeckillAPIView(APIView):
    @transaction.atomic
    def post(self, request):
        pass
    
    
    
################ 局部使用事务#####################
from django.db import transaction
def seckill(request):
    with transaction.atomic():
        save()
    	update()
    return HttpResponse('秒杀成功')

2 事物的回滚和保存点

# 1 普通事务操作(手动操作)
transaction.atomic()  # 开启事务
transaction.commit()  # 提交事务
transaction.rollback() # 回滚事务

# 2 可以使用上下文管理器来控制(自动操作)
with transaction.atomic():  # 自动提交和回滚
    
    
    
    
# 3 保存点:让一个事务可以分段,分段提交,分段回顾
	-开启事务
    干了点事
    设置保存点1
    干了点事
    设置一个保存点2
    干了点事
    
    回滚到干完第二个事,回滚到保存点2
    
    
'''
在事务操作中,我们还会经常显式地设置保存点(savepoint)
一旦发生异常或错误,我们使用savepoint_rollback方法让程序回滚到指定的保存点
如果没有问题,就使用savepoint_commit方法提交事务
'''

from .models import Book
from django.db import transaction
def seckill(request):
    with transaction.atomic():
        # 设置回滚点,一定要开启事务
        sid = transaction.savepoint()
        print(sid)
        try:
            book = Book.objects.get(pk=1)
            book.name = '红楼梦'
            book.save()
        except Exception as e:
            # 如发生异常,回滚到指定地方
            transaction.savepoint_rollback(sid)
            print('出异常了,回滚')
        # 如果没有异常,显式地提交一次事务
        transaction.savepoint_commit(sid)
    return HttpResponse('秒杀成功')


transaction.atomic()  # 开启事务
sid = transaction.savepoint() # 设置保存点
transaction.savepoint_rollback(sid) # 回滚到保存点
transaction.savepoint_commit(sid) #提交保存点

 

3 事务提交后,执行某个回调函数

# 有的时候我们希望当前事务提交后立即执行额外的任务,比如客户下订单后立即邮件通知卖家
###### 案例一:同步##################
def send_email():
    print('发送邮件给卖家了')
    time.sleep(10)
def seckill(request):
    with transaction.atomic():
        # 设置回滚点,一定要开启事务
        sid = transaction.savepoint()
        print(sid)
        try:
            book = Book.objects.get(pk=1)
            book.count = book.count-1
            book.save()
            # raise Exception('不能卖')
        except Exception as e:
            # 如发生异常,回滚到指定地方
            transaction.savepoint_rollback(sid)
        else:
            transaction.savepoint_commit(sid)
            # transaction.on_commit(send_email) # 执行的这个任务是同步的---》同步调用
            # 只要事务提交了--》就会执行 send_email()
            
            transaction.on_commit(lambda: send_sms.delay('1898288322'))
            # 只要事务提交了--》就会执行 lambda: send_sms.delay('1898288322')()
    return HttpResponse('秒杀成功')


##### 案例二:异步 celery中使用###
transaction.on_commit(lambda: send_sms.delay('1898288322'))

4 django实现悲观锁乐观锁案例

# 线上卖图书
	-图书表  图书名字,图书价格,库存字段
    -订单表: 订单id,订单名字
    
# 表准备
class Book(models.Model):
        name = models.CharField(max_length=32)
        price = models.IntegerField()  #
        count = models.SmallIntegerField(verbose_name='库存')
class Order(models.Model):
        order_id = models.CharField(max_length=64)
        order_name = models.CharField(max_length=32)
        
# 使用mysql
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'lqz',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'lqz',
        'PASSWORD': '123',
    }
}

# 创建lqz数据库

4.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; #提交事务

4.2 orm实现上述

#1  使用悲观锁实现下单
@transaction.atomic  # 整个过程在一个事物中---》改两个表:book表减库存,订单表生成记录
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('库存不足,秒杀失败')
  • test.py 测试秒杀
from threading import Thread
import requests
def task():
    res=requests.get('http://127.0.0.1:8000/seckill/')
    print(res.text)

if __name__ == '__main__':
    l=[]
    for i in range(10):
        t=Thread(target=task)
        t.start()
        l.append(t)

    for i in l:
        i.join()

4.2 乐观锁秒杀--》库存还有,有的人就没成功

# 2 乐观锁秒杀--普通版
@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('库存不足,秒杀失败')

4.4异步+事务+乐观锁

@transaction.atomic
def seckill(request):
    res=order_create.delay()
    return HttpResponse(res)


@app.task
def order_create():
    while True:
        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 '秒杀成功'
            else:  # 修改不成功,回滚
                transaction.savepoint_rollback(sid)
                print('被别人扣减了,继续秒杀')
                continue
        else:
            transaction.savepoint_rollback(sid)
            return '库存不足,秒杀失败'
image-20240627171446419
posted @ 2024-06-27 17:37  -半城烟雨  阅读(0)  评论(0编辑  收藏  举报