Mysql乐观锁和悲观锁

我就是我 2022-05-12 09:14 422阅读 0赞

前言

https://blog.csdn.net/xz0125pr/article/details/51698507
https://www.cnblogs.com/laoyeye/p/8097684.html

https://www.cnblogs.com/boblogsbo/p/5602122.html

https://blog.csdn.net/top_code/article/details/56842746

不使用锁会产生的问题

1.1、 丢失更新:

一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新。

1、2、 脏读:

当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6。

1、悲观锁(先关闭数据库自动提交功能)

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

1.1、悲观锁例子

Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。

1.2、注意事项:

1.2.1、行锁和表锁

MySQL InnoDB默认Row-Level Lock,
只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据)
否则MySQL 将会执行Table Lock (将整个数据表给锁住)。

1.2.2、索引和上面的主键是一致的

使用索引也会影响数据库的锁定级别,只要是明确指定的索引,也是只会锁住被选取的数据,否则就会将整个数据表锁住

1、3、共享锁和排它锁

共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。update,insert,delete语句会自动加排它锁的原因

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

上面两种都可以直接通过select …from…查询数据,因为普通查询没有任何锁机制

1.3.1、共享锁

  1. SELECT * from city where id = "1" lock in share mode;
  2. 然后在另一个查询窗口中,对id1的数据进行更新
  3. update city set name="666" where id ="1";
  4. 此时,操作界面进入了卡顿状态,过几秒后,也提示错误信息
  5. [SQL]update city set name="666" where id ="1";
  6. [Err] 1205 - Lock wait timeout exceeded; try restarting transaction
  7. 那么证明,对于id=1的记录加锁成功了,在上一条记录还没有commit之前,这条id=1的记录被锁住了,只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。
  8. update city set name="666" where id ="1" lock in share mode;
  9. [Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1
  10. 加上共享锁后,也提示错误信息了,通过查询资料才知道,对于update,insert,delete语句会自动加排它锁的原因
  11. 于是,我又试了试SELECT * from city where id = "1" lock in share mode;这下成功了。

1.3.2、排它锁

排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。
与共享锁类型,在需要执行的语句后面加上for update就可以了

1、3、使用方法

SELECT … LOCK IN SHARE MODE

SELECT … FOR UPDATE

1.3、缺点

因为悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是对长事务而言,这样的开销往往无法承受。所以与悲观锁相对的,我们有了乐观锁。

2、乐观锁

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。

使用场景:

乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。

2.1、乐观锁一般来说有以下2种方式:

2.1. 使用数据版本(Version)记录机制实现

这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

2.2. 使用时间戳(timestamp)。

乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

2.2、乐观锁例子

Java JUC中的atomic包就是乐观锁的一种实现,AtomicInteger 通过CAS(Compare And Set)操作实现线程安全的自增。

2.3、乐观锁举例

银行两操作员同时操作同一账户。
比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交。最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050。这就是典型的并发问题。

乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个version字段,当前值为1;而当前帐户余额字段(balance)为1000元。假设操作员A先更新完,操作员B后更新。

  1. a、操作员A此时将其读出(version=1),并从其帐户余额中增加100(1000+100=1100)。<br/>
  2. b、在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并从其帐户余额中扣除50(1000-50=950)。
  3. c、操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户增加后余额(balance=1100),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录version更新为2。<br/>
  4. d、操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数据(balance=950),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足 “提交版本必须大于记录当前版本才能执行更新 “的乐观锁策略,因此,操作员B的提交被驳回。<br/>
  5. 这样,就避免了操作员B用基于version=1的旧数据修改的结果覆盖操作员A的操作结果的可能。

2.4、举例测试(和上面的例子不一样)

2.4.1、建表语句

  1. CREATE TABLE `person` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(255) DEFAULT NULL,
  4. `pwd` varchar(255) DEFAULT NULL,
  5. version bigint(20) default 0.
  6. PRIMARY KEY (`id`)
  7. )

2.4.2、repository

  1. import com.hlj.springboot.dome.common.bean.Person;
  2. import org.springframework.data.jpa.repository.Modifying;
  3. import org.springframework.data.jpa.repository.Query;
  4. import org.springframework.data.repository.CrudRepository;
  5. import org.springframework.transaction.annotation.Transactional;
  6. import java.util.List;
  7. @Transactional
  8. public interface PersonRepository extends CrudRepository<Person,Long> {
  9. List<Person> findAll();
  10. @Modifying
  11. @Query(
  12. value = "UPDATE person p set p.name = ?1 ,p.version = p.version + 1 WHERE p .id = ?2 and p.version = ?3",nativeQuery = true
  13. )
  14. int updateLockTest(String name,Long id ,Long version);
  15. }

2.4.3、controller测试

  1. }
  2. //http://localhost:8080/updateLockTest?name=healerjean&id=1&version=5
  3. @GetMapping("updateLockTest")
  4. @ResponseBody
  5. public ResponseBean updateLockTest( String name,Long id ,Long version){
  6. try {
  7. for(int i = 1; i<30 ;i++ ){
  8. new Thread( ()->{
  9. int m = personRepository.updateLockTest(name,id,version);
  10. System.out.println(m);
  11. }).start();
  12. }
  13. return ResponseBean.buildSuccess();
  14. }catch (Exception e){
  15. return ResponseBean.buildFailure(e.getMessage());
  16. }
  17. }

ContactAuthor

发表评论

表情:
评论列表 (有 0 条评论,422人围观)

还没有评论,来说两句吧...

相关阅读

    相关 乐观悲观

    这个问题经常在面试中被问到,在此做个记录! 一.乐观锁 ​ 乐观锁就是总是认为事情总是朝着好的方向发展,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不

    相关 悲观乐观

    锁( locking ) 业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算 处理中,我们希望针对某个 cut-off 时间点的数据进行处理,而不希

    相关 乐观悲观

    乐观锁和悲观锁 数据库管理系统(DBMS)中的并发控制的是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和一致性以及统一性。 乐观并发控制(乐观锁)和悲观并

    相关 悲观乐观

    1、悲观锁:    顾名思义,每次读取数据库的数据时,都假设会被它人修改,因此要加锁将数据锁住,防止被修改。   可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加

    相关 悲观乐观

    这是一篇介绍悲观锁和乐观锁的入门文章。旨在让那些不了解悲观锁和乐观锁的小白们弄清楚什么是悲观锁,什么是乐观锁。不同于其他文章,本文会配上相应的图解让大家更容易理解。通过该文,你