跳至主要內容

乐观锁和悲观锁

微信公众号:储凡About 12 min

乐观锁和悲观锁

乐观锁的实现

  • 版本号机制
  • 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问题------> 互斥同步比原子类更加高效

数据库隔离级别

参考:https://www.cnblogs.com/yubaolee/p/10398633.htmlopen in new window

读未提交(Read Uncommitted)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。 因为采用这种隔离级别只能防止第一类更新丢失问题,不能解决脏读,不可重复读及幻读问题。

读已提交(Read Committed)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。 这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题。

可重复读(Repeatable Read)

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。 这种隔离级别可以防止除幻读外的其他问题。

可串行化(Serializable)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。 在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争。 通常数据库不会用这个隔离级别,需要其他的机制来解决这些问题:乐观锁和悲观锁