首页 > 编程知识 正文

什么是锁 乐观 悲观 原理,Mysql乐观锁

时间:2023-05-04 19:43:58 阅读:135940 作者:2397

一、前言在理解悲观摇滚和乐观摇滚之前,先理解什么是摇滚,为什么要用摇滚吧。

技术来自生活。 钥匙不仅在程序上,在现实中也无处不在。 例如,我们上下班打卡的指纹钥匙、保险箱的密码钥匙,还有我们注册的用户名和密码也是钥匙。 在生活中使用密钥可以保护我们的人身安全(指纹密钥)、财产安全(保险箱密码密钥)、信息安全(用户名密码密钥),让我们更安心地使用生活。 因为有钥匙,所以我们

程序中的锁是确保我们数据安全的机制和手段,例如,如果有多个线程访问以修改共享变量,则可以锁定修改操作。 (syncronized)。 如果多个用户修改表中的同一数据,则可以对该行中的数据上锁(行锁)。 因此,如果程序可能同时运行,则需要一种方法来确保同时运行时数据的准确性。 通过此手段,如果当前用户与其他用户一起操作,则得到的结果与他单独操作时的结果相同

如果不能同时控制,可能会导致脏读、幻读、不可重复读等问题,如下图所示。

如果由于并发操作,未锁定以进行并发控制,则数据库的最终数据可能为3或5,从而导致数值不准确

二、悲观锁定和乐观锁定首先要明确的是,无论是悲观锁定还是乐观锁定,都被认为是人们定义的概念、思想。

2.1、悲观摇滚悲观锁(Pessimistic Lock):悲观,以为每次去拿数据都会被别人修改。 所以每次拿数据都要锁门。 这样的话,在悲观的锁定被解除之前,其他人试图取数据会被屏蔽,悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程

但是在效率方面,处理锁定机制会产生额外的开销,从而增加发生死锁的机会。 另外,并行性也降低,已经有一个线程a被锁定时,其他线程必须等到该线程a被处理

数据库中行锁、表锁、读锁、写锁、独占锁以及在syncronized中实现的锁都是悲观锁

悲观控制实际上是“先取锁再访问”的保守策略,保证了数据处理的安全。

2.2、乐观摇滚乐观锁(Optimistic Lock):乐观,每次去拿数据都觉得别人不会修改。 虽然不会锁定,但尝试更新数据时,系统会在更新前检查从读取到更新之间的其他人是否修改了该数据。 如果进行了修改,请重新读取,再次尝试更新,然后重复上述步骤直到更新成功。 当然,也允许更新失败的线程放弃操作。 乐观锁定可以应用于多种读取的APP应用类型,以提高吞吐量

相对于悲观锁定,乐观锁定在处理数据库时不使用数据库提供的锁定机制。 实现乐观锁定的常用方法是记录数据版本(version )或时间戳,但最常见的是使用版本记录。

乐观控件不会发生锁定或死锁,因为它被认为事务之间发生数据冲突(data race )的概率相对较低,因此会尽可能地执行,直到提交为止才锁定。

三、手链的实现悲观锁阻塞事务、乐观锁回滚重试:它们各有优缺点,不要认为一方一定比另一方优越。 乐观锁定适用于写入相对较少(即冲突真正较少)的情况,可以节省锁定开销,提高系统整体吞吐量。 但是,如果冲突频繁发生,上位APP程序会不断地进行重试,反而会降低性能,所以在这种情况下使用悲观的锁比较合适。

3.1悲观锁定的实现方式场景:

有用户a和用户b,在同一店铺去买同一商品,但商品的可购买数量只有一个

以下是这家店铺的商品表t_goods的结构和表的数据:

如果用户a和用户b同时订购而没有锁定,则会报告错误。

悲观锁定的实现往往依赖于数据库提供的锁定机制。 在数据库中,如何用悲观的锁来解决这个问题呢?

用户a根据订单购买商品(臭豆腐)时,首先尝试对其数据(臭豆腐)进行悲观锁定失败。 商品)臭豆腐)表示已被其他事务修改。 当前呼叫需要等待或抛出异常。 具体的返回方式需要开发者根据情况成功锁定。 商品)臭豆腐)只有用户a修改(用户b想买) )臭豆腐用户a购买后,用户b再次打算去买(臭豆腐)时,发现数量为0。 b看到后就放弃购买。 在此期间,其他数据)中如果有修改或锁定的操作,则解除锁定后或者直接抛出异常

如何锁住悲观的锁? 以下语句可以对id=2的行数据执行悲观锁定,并首先关闭MySQL数据库的自动提交属性: Mysql默认使用自动提交模式。 这意味着执行更新操作时,MySQL会立即提交结果。 (SQL语句: set autocommit=0)

33558 www.Sina.com/select num fromt _ goodswhereid=2for update

我们使用了mysql的

两个会话,也就是两个命令行来演示:

事务A:
我们可以看到数据是立刻马上就可以查询出来,num=1

事务B:
我们是可以看到,事务B会一直等待事务A释放锁。如果事务A长期不释放锁,那么最终事务B将会报错,报错如下:Lock wait timeout exceeded; try restarting transaction,表示语句已被锁住

现在我们让事务A执行命令去修改数据,让臭豆腐的数量减一,然后查看修改后的数据,最后commit,结束事务

我们可以看到当我们事务A执行完成之后,臭豆腐的库存只有0个了,这个时候我们用户B再来购买这个臭豆腐的时候就会发现,最后一个臭豆腐已经被用户A购买完了,那么用户B只能放弃购买臭豆腐了。

通过悲观锁我们可以解决因为商品库存不足,导致的商品超出库存的售卖。

3.1 乐观锁的实现方式

对于上面的应用场景,我们应该怎么用乐观锁去解决呢?在上面的乐观锁中,我们有提到使用版本号(version)来解决,所以我们需要在t_goods加上版本号,调整后的sql表结构如下:

具体操作步骤如下:
1、首先用户A和用户B同时将臭豆腐(id=2)的数据查出来
2、然后用户A先买,用户A将(id=1和version=0)作为条件进行数据更新,将数量-1,并且将版本号+1。此时版本号变为1。用户A此时就完成了商品的购买
3、 用户B开始买,用户B也将(id=1和version=0)作为条件进行数据更新
4、更新完后,发现更新的数据行数为0,此时就说明已经有人改动过数据,此时就应该提示用户B重新查看最新数据购买

1、首先我们开启两个会话窗口,输入查询语句:select num from t_goods where id = 2
事务A:

事务B:

这个时候事务A和事务B同时获取相同的数据

2、此时事务A进行更新数据的操作,然后在查询更新后的数据

这个时候我们可以看到事务A更新成功,并且库存-1 版本号+1成功

2、此时事务B进行更新数据的操作,然后在查询更新后的数据

可以看到最终修改的时候失败,数据没有改变。此时就需要我们告知用户B重新处理

3.1.1 CAS

说到乐观锁,就必须提到一个概念:CAS
什么是CAS呢?Compare-and-Swap,即比较并替换,也有叫做Compare-and-Set的,比较并设置。
1、比较:读取到了一个值A,在将其更新为B之前,检查原值是否仍为A(未被其他线程改动)。
2、设置:如果是,将A更新为B,结束。[1]如果不是,则什么都不做。
上面的两步操作是原子性的,可以简单地理解为瞬间完成,在CPU看来就是一步操作。
有了CAS,就可以实现一个乐观锁,允许多个线程同时读取(因为根本没有加锁操作),但是只有一个线程可以成功更新数据,并导致其他要更新数据的线程回滚重试。 CAS利用CPU指令,从硬件层面保证了操作的原子性,以达到类似于锁的效果。

Java中真正的CAS操作调用的native方法
因为整个过程中并没有“加锁”和“解锁”操作,因此乐观锁策略也被称为无锁编程。换句话说,乐观锁其实不是“锁”,它仅仅是一个循环重试CAS的算法而已,但是CAS有一个问题那就是会产生ABA问题,什么是ABA问题,以及如何解决呢?

ABA 问题:
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

ABA 问题解决:
我们需要加上一个版本号(Version),在每次提交的时候将版本号+1操作,那么下个线程去提交修改的时候,会带上版本号去判断,如果版本修改了,那么线程重试或者提示错误信息~

四、如何选择

悲观锁阻塞事务,乐观锁回滚重试,它们各有优缺点,不要认为一种一定好于另一种。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。

但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

注意点:

1、乐观锁并未真正加锁,所以效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。

2、悲观锁依赖数据库锁,效率低。更新失败的概率比较低。

五、总结

这篇文章讲解了悲观锁与乐观锁的区别,以及实现场景,不管是悲观锁还是乐观锁都是人们定义出来的概念,是一种思想,如何有有疑问或者问题的小伙伴可以在下面进行留言,hhdlm看到了会第一时间回复大家,谢谢,大家加油~

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。