首页 > 其他分享 >悲观锁和乐观锁

悲观锁和乐观锁

时间:2023-08-28 14:55:22浏览次数:37  
标签:count transaction 乐观 并发 库存 悲观 book

目录

一 概念

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

1.1 什么是并发控制

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

1.2 悲观锁

悲观锁:当要对一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发,让并行变成串行
    
-这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制【Pessimistic Concurrency Control,缩写PCC,又名悲观锁】
- 之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。
    
-悲观锁实现方式:
	1 传统的关系型数据库(如mysql)使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
	2 编程语言中的线程锁,比如python的互斥锁
	3 分布式锁:redis实现的分布式锁等

1.3 乐观锁

    通过程序实现(没有真正的一把锁),不会产生死锁
    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据,如果更新了,我们就不做修改
        
-乐观锁实现方案:
    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.4 悲观锁乐观锁使用场景

并发量:如果并发量不大,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,建议乐观锁
响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高
读多写少: 乐观锁适用于读多写少的应用场景,这样可以提高并发粒度
冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大
重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低

二 MySQL实现悲观锁

start transaction;  # 开启事务
# begin; # 开启事务,简写

select * from goods where id = 1 for update;  # 行锁,对于查询出的一条或者多条结果加行锁(for update是表示加锁)

# order表中加数据

update goods set stock = stock - 1 where id = 1; # 更新

commit; #提交事务

三 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数据库,执行数据迁移命令
python3.9 manage.py makemigrations
python3.9 manage.py migrate

3.1 django实现悲观锁

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

3.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:  # 影响的行数是1,表示修改成功
            transaction.savepoint_commit(sid)  # 提交保存点
            return HttpResponse('秒杀成功')
        else:  # 修改不成功,回滚
            transaction.savepoint_rollback(sid) 
            return HttpResponse('被别人改了,回滚,秒杀失败')
    else:
        transaction.savepoint_rollback(sid)
        return HttpResponse('库存不足,秒杀失败')
    
# 但是会产生一个情况:库存还有,有的人就没成功

3.3 升级版

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

标签:count,transaction,乐观,并发,库存,悲观,book
From: https://www.cnblogs.com/zjyao/p/17662269.html

相关文章

  • The database operation was expected to affect 1 row(s), but actually affected 0
    Thedatabaseoperationwasexpectedtoaffect1row(s),butactuallyaffected0row(s);解决乐观并发1.乐观并发EFCore实现乐观并发,假定并发冲突相对较少。与悲观方法(即先锁定数据,然后才继续修改数据)不同,乐观并发不需要锁定,而是安排数据修改在保存时失败(如果数据自......
  • django乐观锁、悲观锁商品秒杀简单demo
    悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读......
  • 关于乐观锁上锁成功的前提条件讨论
    在计算机编程领域,乐观锁(OptimisticLocking)是一种处理并发访问共享数据的策略,它的基本思想是先进行操作,然后在更新数据之前检查是否有其他并发操作修改了数据。如果没有冲突,操作可以成功执行;如果存在冲突,系统需要进行冲突处理,例如回滚操作、重新尝试或者通知用户。乐观锁的设计目......
  • 什么是计算机编程领域的乐观锁和悲观锁
    乐观锁和悲观锁是计算机编程领域中用于处理并发访问数据的两种不同策略。它们的主要目标是在多个线程或进程同时访问共享数据时,保证数据的一致性和完整性,避免出现竞态条件(RaceCondition)。在不同的情景下,选择合适的锁策略可以提高程序的性能和可靠性。1.悲观锁(PessimisticLocki......
  • 数据库悲观锁和乐观锁
    一下是转载的oracle和Mysql两种数据库悲观锁和乐观锁机制及乐观锁实现方式:一、OracleOracle数据库悲观锁与乐观锁是本文我们主要要介绍的内容。有时候为了得到最大的性能,一般数据库都有并发机制,不过带来的问题就是数据访问的冲突。为了解决这个问题,大多数数据库用的方法就是数据的......
  • 乐观锁的学习
    和分页查询同理采用的是拦截器interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());利用拦截器判断数据是否被修改,添加版本字段version,在实体类中添加属性然后添加注解@version添加字段即可,当两个查询和修改请求一同发起时version值大于1就不会再被修改......
  • 乐观锁-并发现象怎么控制数据修改-类似秒杀
                ......
  • Java并发(十四)----悲观互斥与乐观重试
    1.悲观互斥互斥实际是悲观锁的思想例如,有下面取款的需求interfaceAccount{  //获取余额  IntegergetBalance();​  //取款  voidwithdraw(Integeramount);​  /**  *方法内会启动1000个线程,每个线程做-10元的操作  *如......
  • Mysql 悲观锁
    1.mysql的悲观锁概念悲观锁[PessimisticConcurrencyControl]指的是在操作数据的时候悲观的认为数据会发生冲突,所以在每次操作的时候都直接把数据给锁住,这样其他的线程就只能阻塞住无法操作,所以悲观锁比较耗费时间, 一般悲观锁都是借助数据库锁机制 在java中synchr......
  • 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁
    最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类:公平锁/非公平锁可重入锁独享锁/共享锁乐观锁/悲观锁分段锁自旋锁 最全Java锁详解:独享锁/共享锁+公平锁/非......