乐观锁和悲观锁
乐观锁和悲观锁
乐观锁的实现
- 版本号机制
- CAS算法
都是采用预期值和原来的值进行比较,相同则允许操作。
什么场景下需要使用锁?
在多节点部署或者多线程执行时,同一个时间可能有多个线程更新相同数据,产生冲突,这就是并发问题。这样的情况下会出现以下问题:
更新丢失
(分两类):一个事务更新数据后,被另一个更新数据的事务覆盖。脏读
:一个事务读取另一个事物未提交的数据,即为脏读。幻读
:B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样不可重复读
:B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,B事务的这两次读取出来的数据不一样
针对并发引入并发控制机制,即加锁。
加锁的目的是在同一个时间只有一个事务在更新数据,通过锁独占数据的修改权。
乐观锁:不会发生并发抢占资源,只有在提交操作的时候检查是否违反数据完整性。只能防止脏读后数据的提交,不能解决脏读
悲观锁:一定会有并发抢占资源,强行独占资源,在整个数据处理过程中,将数据处于锁定状态
版本号控制
使用version
版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。
CAS算法
Compare And Swap
: 先比较再进行交换,CAS
算法包含三个操作数:
内存位置(V)
预期原值(A)
新值(B)
如果内存位置的值V
与预期原值A
相匹配,那么处理器会将该位置值更新为新值B
。否则,处理器不做任何操作,会循环比较直到相等,整个比较赋值操作是一个原子操作
。
有一点像在缓存中,添加标记去顶当前数据版本号,与预期原值进行比较。
CAS缺点:
循环时间开销大
:当内存地址V与预期值B不相等时会一直循环比较直到相等;只能保证一个共享变量的原子操作
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,就能说明它的值没有被其他线程修改过吗?
很明显不是,因为在这段时间内它的值可能被改为其他值, 然后又被改回A,那CAS操作就会认为它从来没被改过,这个问题就被称为
CAS算法
操作的ABA
问题;
ABA问题------> 互斥同步比原子类更加高效
数据库隔离级别
读未提交(Read Uncommitted)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。 因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题。
读已提交(Read Committed)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。 这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题。
可重复读(Repeatable Read)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。 这种隔离级别可以防止除幻读外的其他问题。
可串行化(Serializable)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。 在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争。 通常数据库不会用这个隔离级别,需要其他的机制来解决这些问题:乐观锁和悲观锁