ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

悲观锁和乐观锁

2020-01-22 09:01:16  阅读:260  来源: 互联网

标签:库存 版本号 数据库 更新 乐观 线程 悲观


悲观锁和乐观锁

概念

乐观锁和悲观锁都是一种思想,并不是真实存在于数据库中的一种机制。

悲观锁 PCC(Pessimistic Concurrency Control)

顾名思义,就是很悲观,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改数据,所以每次在拿数据的时候都会上锁,别人只能等待,直到我释放锁才能拿到锁;传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁,java 中的 synchronized 和 ReentrantLock 也是悲观锁的思想。

我们认为系统中的并发更新会非常频繁,并且事务失败了以后重来的开销很大,这样以来,我们就需要采用真正意义上的锁来进行实现。悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等以前的事务提交或者回滚释放锁。在效率方面,处理锁的操作会产生额外的开销,而且增加了死锁的可能。当一个线程在处理某行数据的时候,其它线程只能等待。

实现方式

悲观锁的实现是依赖于数据库提供的锁机制,流程如下:

  1. 修改记录前,对记录加上排他锁(exclusive locking)
  2. 如果加锁失败,说明这条数据正在被修改,那么当前查询要等待或者抛出异常,这由开发者决定
  3. 如果加锁成功,可以对这条数据修改了,事务完成解锁
  4. 加锁修改期间,其他事务也想对这条记录进行操作时,都要等待或者直接抛出异常

注意:在使用 mysql Innodb 引擎实现悲观锁时,必须关闭 mysql 的自动提交属性,因为 MySQL 默认使用 autocommit 模式,也就是说,当你执行一个更新操作后,MySQL 会立刻将结果进行提交。执行:set autocommit = 0;

我们平时大多在数据库层面实现加锁操作,比如 JDBC 方式:在 JDBC 中使用悲观锁,需要使用 select for update 语句。

例:下单扣减库存如何使用悲观锁实现

//0.开始事务
begin; 
//1.查询出商品库存信息
select quantity from items where id = 1 for update;
//2.修改商品库存为2
update items set quantity = 2 where id = 1;
//3.提交事务
commit;

使用 select for update 会把数据锁住。MySQL InnoDB 默认行级锁,行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

乐观锁

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。它的基本思想就是每次提交一个事务更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败,返回错误的信息,让用户决定如何去做。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于 write_condition 机制的其实都是提供的乐观锁。乐观锁不会使用数据库提供的锁机制,一般基于版本号机制实现。

实现方式

大多是基于数据版本(Version)记录机制实现,何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
乐观锁的实现不需要借助于数据库锁机制,只要就是两个步骤:冲突检测和数据更新,其中一种典型的是实现方法就是CAS(Compare and Swap)

CAS实现乐观锁

CAS是一种乐观锁实现方式,顾名思义就是先比较后更新。在对一个数据进行更新前,先持有对这个数据原有值的备份。比如,要将a=2更新为a=3,在进行更新前会比较此刻a是否为2.如果是2,才会进行更新操作。当多个线程尝试使用CAS同时更新一个变量时,只有一个线程能够成功,其余都是失败。失败的线程不会被挂起,而是被告知这次竞争失败,并且可以再次尝试。
比如前面的扣减库存问题,乐观锁方式实现如下:

//查询出商品库存信息,quantity = 3
select quantity from items where id = 1
//修改商品库存为2
update items set quantity = 2 where id = 1 and quantity = 3;

在更新之前,先查询库存表中当前库存数,然后在做 update 时,以库存数作为一个修改条件。当进行提交更新的时候,判断数据库的当前库存数与第一次取出来的库存数进行比对,相等则更新,否则认为是过期数据。
但是这种更新存在一个比较严重的问题,即 ABA 问题

ABA问题
A线程去除库存数3,B线程取出库存数3,B线程先将库存数变为2,又将库存数变为3,A线程在进行更新操作时发现库存是仍然是3,然后操作成功。尽管A线程操作是成功的,但是不能代表这个过程就是没问题的

解决ABA问题的一个方法是通过一个顺序递增的 version 字段:

//查询出商品信息,version = 1
select version from items where id = 1
//修改商品库存为2
update items set quantity = 2,version = 2 where id = 1 and version = 1;

在每次执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据版本号一致就可以执行修改操作并对版本号执行+1操作,否则执行失败。因为每次修改操作都会将版本号增加,所以不会出现ABA问题。还可以使用时间戳,因为时间戳天然具有顺序递增性。

异同

  • 乐观锁并不是真正的加锁,优点是效率高,缺点是更新失败的概率比较高;
  • 悲观锁依赖于数据库锁机制,更新失败的概率比较低,但是效率也低(看使用场景,在竞争高的情况下,乐观锁的效率反而会更低)。

应用场景

  • 乐观锁适用于读多写少的情况,即冲突很少发生;
  • 如果是多写的情况,应用会不断重试,反而会降低系统性能,这种情况最好用悲观锁,因为等待到锁被释放后,可以立即获得锁进行操作。
MCJPAO 发布了42 篇原创文章 · 获赞 10 · 访问量 7032 私信 关注

标签:库存,版本号,数据库,更新,乐观,线程,悲观
来源: https://blog.csdn.net/MCJPAO/article/details/104066322

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有