title: 乐观锁和悲观锁 date: 2017-03-03T00:00:00+08:00 draft: false categories: [数据库,系统设计] tags: [数据库,系统设计]


锁机制经常用于业务场景中的并发控制,那么锁应该怎么用呢?

# 为什么需要锁

锁机制经常用于业务场景中的并发控制

在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。

# 典型的冲突

# 丢失更新

一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。

例如:用户A把值从6改为2,用户B把值从2改为6,则用户A丢失了他的更新。

# 脏读

当一个事务读取其它完成一半事务的记录时,就会发生脏读取。

例如:用户A,B看到的值都是6,用户B把值改为2,用户A读到的值仍为6。

为了解决这些并发带来的问题。 我们需要引入并发控制机制。

# 悲观锁(Pessimistic Lock)

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

顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

悲观锁,可以解决丢失更新和脏读问题

# 乐观锁(Optimistic Lock)

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

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据, 可以使用版本号或timestamp等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

乐观锁,不能解决脏读的问题。

# 取舍

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。 但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法。

# CAS算法

乐观锁用到的机制就是CAS操作,Compare & Set,或是 Compare & Swap

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。 当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 这个过程是原子性的。

# ABA问题

如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。