并发控制

当程序中可能出现并发的情况时,我们就需要通过一定的手段来保证在并发情况下数据的准确性,通过这种手段保证了当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。

这种手段就叫做并发控制。

并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。

没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。


实现并发控制的手段

实现并发控制的主要手段大致可以分为乐观并发控制和悲观并发控制两种。

在开始介绍之前要明确一下:无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。

其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像hibernate、tair、memcache等都有类似的概念。

所以,不应该拿乐观锁、悲观锁和其他的数据库锁等进行对比

在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。

另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。



悲观锁(Pessimistic Lock)

总是假设最坏的情况,当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。

共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程

这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制

(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)。

MySQL 使用悲观锁

原生的 SQL 语句

  1. 开启事务
  2. 查询的时候加锁 --->select * from user where id =1 for update
  3. 当结束事务锁就被释放

Django 中使用

  1. 开始事务
  2. 在查询的时候 ---> User.objects.select_for_update().filter(id =1 ).first()
  3. 可以对数据进行修改,数据永远不会被别人修改,只有你自己能动
  4. 当结束事务锁就被释放


乐观锁( Optimistic Locking )

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测 (可以使用版本号机制和CAS算法实现,乐观锁适用于多读的应用类型,这样可以提高吞吐量) ,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。

版本号机制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。 当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

即compare and swap(即先比较再交换),是一种有名的无锁算法。

无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。

CAS算法涉及到三个操作数

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

乐观锁的缺点

1 ABA 问题是乐观锁一个常见的问题

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?

很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。

这个问题被称为CAS操作的 "ABA"问题。

2 循环时间长开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销

3 只能保证一个共享变量的原子操作


mysql 使用乐观锁(如果使用乐观锁,mysql 隔离级别要变成 read committed,在 django2.0 以后,用乐观锁不需要修改隔离级别)

乐观本质不是锁,是通过代码级别实现数据安全

  1. 开始事务

  2. 查询的时候不要做任何操作 data= User.objects.filter(id =1 ).first() 将这个条数据中的 age 在原来的基础上加1

  3. 在修改数据的时候 User.objects.filter(id = 1 ,age = data.age).update(age = data.age+1) 从而保证在我查询到我修改的这段时候,没有人动过我的数据。

  4. 如果 3 中的更新的影响行数为 1,说明数据没有被别人改动,只我动,如果3中的影响行数为0,说明该数据已经被别人修改。

    这时我们想要改数据,就必须重复执行2-3两不,直到3有影响行数。

 posted on 2020-03-18 08:49  Rannie`  阅读(199)  评论(0编辑  收藏  举报
去除动画
找回动画