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

1 悲观锁乐观锁
1.1 并发控制
1.1 悲观锁
1.2 乐观锁
1.3 悲观锁乐观锁使用场景

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

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

4 django使用乐观锁
4.1 普通版
4.2 升级版

1 悲观锁乐观锁

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

1.1 并发控制

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

1.1 悲观

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


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

1.2 乐观锁

# 乐观锁是什么?
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开启事务

Django支持事务操作的,它的默认事务行为自动提交,

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

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

2.1 全局开启事务

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请求根本不涉及到事务操作,一个更好的方式是局部开启事务按需使用
​

局部禁用全局事务

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 视图开启事务

Django局部开启事务,借助于transaction.atomic

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

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

2.4 savepoint回滚

'''
在事务操作中,我们还会经常显式地设置保存点(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 事务提交后回调函数

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

###### 案例一##################
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 前置条件,表模型

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)
# 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 模拟秒杀生成订单场景

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

客户端测试

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 普通版

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

客户端测试

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 升级

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

原文地址:https://blog.csdn.net/weixin_44145338/article/details/134697008

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_10517.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注