悲观锁乐观锁在django中使用

[1 悲观锁乐观锁](#1 悲观锁乐观锁)
[1.1 并发控制](#1.1 并发控制)
[1.1 悲观锁](#1.1 悲观锁)
[1.2 乐观锁](#1.2 乐观锁)
[1.3 悲观锁乐观锁使用场景](#1.3 悲观锁乐观锁使用场景)

[2 django中开启事务](#2 django中开启事务)
[2.1 全局开启事务](#2.1 全局开启事务)
[2.2 视图开启事务](#2.2 视图开启事务)
[2.3 局部使用事务](#2.3 局部使用事务)
[2.4 savepoint回滚](#2.4 savepoint回滚)
[2.5 事务提交后回调函数](#2.5 事务提交后回调函数)

[3 django中使用悲观锁](#3 django中使用悲观锁)
[3.1 前置条件,表模型](#3.1 前置条件,表模型)
[3.1 模拟秒杀生成订单场景](#3.1 模拟秒杀生成订单场景)

[4 django使用乐观锁](#4 django使用乐观锁)
[4.1 普通版](#4.1 普通版)
[4.2 升级版](#4.2 升级版)

1 悲观锁乐观锁

无论是悲观锁还是乐观锁,都是人们定义出来的概念,仅仅是一种思想,与语言无关

1.1 并发控制

sql 复制代码
1 并发控制:当程序中出现并发问题时,就需要保证在并发情况下数据的准确性,以此确保当前用户
	和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的,这种手段就叫做并发控制
2 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题

1.1 悲观锁

sql 复制代码
# 悲观锁是什么?
1 悲观锁:当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,
	最好的办法就是直接对该数据进行加锁以防止并发。
2 这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制
	【Pessimistic Concurrency Control,缩写"PCC",又名"悲观锁"】
3 之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。
	总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,
	因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。


# 悲观锁的实现方式:
1 传统的关系型数据库(如mysql)使用这种锁机制,
	比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
2 编程语言中的线程锁,比如python的互斥锁
3 分布式锁:redis实现的分布式锁等
​

1.2 乐观锁

sql 复制代码
# 乐观锁是什么?
1 通过程序实现,不会产生死锁
2 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,
	只在更新的时候会判断一下在此期间别人有没有去更新这个数据
​
# 乐观锁实现方式
1 CAS(Compare And Swap) 即比较并交换。
	是解决多线程并行情况下使用锁造成性能损耗的一种机制,
	CAS 操作包含三个操作数------内存位置(V)、预期原值(A)和新值(B),执行CAS操作的时候,
	将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,
	否则,处理器不做任何操作
  
   CAS会出现ABA问题,比如,你看到桌子上有100块钱,然后你去干其他事了,回来之后看到桌子上
   依然是100块钱,你就认为这100块没人动过,其实在你走的那段时间,别人已经拿走了100块,
   后来又还回来了。这就是ABA问题
  
  解决ABA问题:
  	既然有人动了,那我们对数据加一个版本控制字段,只要有人动过这个数据,就把版本进行增加,
  	我们看到桌子上有100块钱版本是1,回来后发现桌子上100没变,但是版本却是2,
  	就立马明白100块有人动过
​
2 版本号控制:
	在数据表中增加一个版本号字段,每次更新数据时将版本号加1,同时将当前版本号作为更新条件,
	如果当前版本号与更新时的版本号一致,则更新成功,否则更新失败
3 时间戳方式:
	在数据表中增加一个时间戳字段,每次更新数据时将时间戳更新为当前时间戳,
	同时将当前时间戳作为更新条件,如果当前时间戳与更新时的时间戳一致,则更新成功,否则更新失败

1.3 悲观锁乐观锁使用场景

复制代码
并发量:如果并发量不大,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,
	悲观锁定会带来非常大的性能问题, 建议乐观锁

响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,
	不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高
	
冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。
	冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大
	
重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低

读多写少: 乐观锁适用于读多写少的应用场景,这样可以提高并发粒度

2 django中开启事务

sql 复制代码
Django是支持事务操作的,它的默认事务行为是自动提交,

具体表现形式为:每次数据库操作(比如调用save()方法)会立即被提交到数据库中。

但是如果你希望把连续的SQL操作包裹在一个事务里,就需要手动开启事务。

2.1 全局开启事务

sql 复制代码
DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.mysql',
         'NAME': 'lqz',
         'HOST': '127.0.0.1',
         'PORT': '3306',
         'USER': 'lqz',
         'PASSWORD': 'lqz123',
          #全局开启事务,绑定的是http请求响应整个过程
         'ATOMIC_REQUESTS': True, 
     }
}
​

1 每当有请求过来时,Django会在调用视图方法前开启一个事务。
	如果完成了请求处理并正确返回了结果,Django就会提交该事务。否则,Django会回滚该事务。
​
2 虽然全局开启事务很简单,但Django并不推荐开启全局事务。因为一旦将事务跟 HTTP 请求绑定到
	一起时,每一个请求都会开启事务,当访问量增长到一定的时候会造成很大的性能损耗。
	在实际开发过程中,很多GET请求根本不涉及到事务操作,一个更好的方式是局部开启事务按需使用
​

局部禁用全局事务

sql 复制代码
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('秒杀成功')

2.2 视图开启事务

sql 复制代码
Django局部开启事务,借助于transaction.atomic

使用它我们就可以创建一个具备原子性的代码块,一旦代码块正常运行完毕,
所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚
sql 复制代码
# 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

2.3 局部使用事务

sql 复制代码
from django.db import transaction
def seckill(request):
    with transaction.atomic():
        pass
    return HttpResponse('秒杀成功')

2.4 savepoint回滚

sql 复制代码
'''
在事务操作中,我们还会经常显式地设置保存点(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('秒杀成功')

2.5 事务提交后回调函数

sql 复制代码
# 有的时候我们希望当前事务提交后立即执行额外的任务,比如客户下订单后立即邮件通知卖家

###### 案例一##################
def send_email():
    print('发送邮件给卖家了')
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()
        except Exception as e:
            # 如发生异常,回滚到指定地方
            transaction.savepoint_rollback(sid)
        else:
            transaction.savepoint_commit(sid)
            transaction.on_commit(send_email)
    return HttpResponse('秒杀成功')
  
  
##### 案例二:celery中使用###
transaction.on_commit(lambda: send_sms.delay('电话号码'))

3 django中使用悲观锁

3.1 前置条件,表模型

sql 复制代码
from django.db import models
class Book(models.Model):
    name = models.CharField(max_length=32)
    count = models.IntegerField()

class Order(models.Model):
    order_id=models.CharField(max_length=64)
    order_name=models.CharField(max_length=32)
sql 复制代码
# Django中使用悲观锁锁定一个对象,需要使用select_for_update()方法。它本质是一个行级锁,能锁定所有匹配的行,如果查询所有,可以锁住整张表,直到事务结束

@transaction.atomic
def seckill(request):
    # 锁住查询到的book对象,直到事务结束
    book = Book.objects.select_for_update().filter(pk=1).first()
    book.count= book.count-1
    book.save()
    time.sleep(4)
    return HttpResponse('秒杀成功')

3.1 模拟秒杀生成订单场景

sql 复制代码
@transaction.atomic
def seckill(request):
    # 锁住查询到的book对象,直到事务结束
    sid = transaction.savepoint()
    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('库存不足,秒杀失败')

客户端测试

sql 复制代码
import requests
from threading import Thread
def task():
    res = requests.get('http://127.0.0.1:8000/sckill/')
    print(res.text)

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

4 django使用乐观锁

4.1 普通版

sql 复制代码
@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('库存不足,秒杀失败')

客户端测试

sql 复制代码
import requests
from threading import Thread
def task():
    res = requests.get('http://127.0.0.1:8000/sckill/')
    print(res.text)

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

4.2 升级版

sql 复制代码
@transaction.atomic
def seckill(request):
    # 锁住查询到的book对象,直到事务结束
    ### 乐观锁可能会失败,我们一直尝试秒杀,直到秒成功或库存不足
    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 HttpResponse('秒杀成功')
            else:  # 修改不成功,回滚
                transaction.savepoint_rollback(sid)
                print('被别人扣减了,继续秒杀')
                continue

        else:
            transaction.savepoint_rollback(sid)
            return HttpResponse('库存不足,秒杀失败')
相关推荐
心平愈三千疾1 小时前
通俗理解JVM细节-面试篇
java·jvm·数据库·面试
费弗里2 小时前
Python全栈应用开发利器Dash 3.x新版本介绍(1)
python·dash
李少兄9 天前
解决OSS存储桶未创建导致的XML错误
xml·开发语言·python
就叫飞六吧9 天前
基于keepalived、vip实现高可用nginx (centos)
python·nginx·centos
Vertira9 天前
PyTorch中的permute, transpose, view, reshape和flatten函数详解(已解决)
人工智能·pytorch·python
学Linux的语莫9 天前
python基础语法
开发语言·python
我科绝伦(Huanhuan Zhou)9 天前
Oracle|Oracle SQL*Plus 配置上下翻页功能
数据库·sql·oracle
匿名的魔术师9 天前
实验问题记录:PyTorch Tensor 也会出现 a = b 赋值后,修改 a 会影响 b 的情况
人工智能·pytorch·python
Cachel wood9 天前
Spark教程6:Spark 底层执行原理详解
大数据·数据库·分布式·计算机网络·spark
Ven%9 天前
PyTorch 张量(Tensors)全面指南:从基础到实战
人工智能·pytorch·python