如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!博客目录 | 先点这里
!首先声明,MySQL的测试环境是5.7
数据库并发场景有三种,分别为:
读-读
:不存在任何问题,也不需要并发控制读-写
:有隔离性问题,可能遇到脏读,幻读,不可重复读写-写
:可能存更新丢失问题,比如第一类更新丢失,第二类更新丢失所以要是别人再问你乐观锁和悲观锁是什么,你千万别说它是一种具体的锁,它只是一种锁的设计思想,他可以有很多具体的实现类
在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control
,缩写“PCC”
)是一种并发控制的方法; 悲观锁指的是采用一种持悲观消极的态度,默认数据被外界访问时,必然会产生冲突,所以在数据处理的整个过程中都采用加锁的状态,保证同一时间,只有一个线程可以访问到数据,实现数据的排他性;通常,数据库的悲观锁是利用数据库本身提供的锁机制去实现的.
数据库的悲观并发控制可以解决读-写
冲突和写-写
冲突,指在用加锁的方式去解决
通常情况下,数据库的悲观锁就是利用数据库本身提供的锁去实现的
当然数据库提供了非常多的锁,每种数据库提供的锁也不尽然相同,所以具体情况就要看是什么锁了,比如行锁,表锁等
引用@Hollis文章原话
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数
优点:
适合在写多读少的并发环境中使用,虽然无法维持非常高的性能,但是在乐观锁无法提更好的性能前提下,可以做到数据的安全性
缺点:
加锁会增加系统开销,虽然能保证数据的安全,但数据处理吞吐量低,不适合在读书写少的场合下使用
在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control
,缩写“OCC”
)是一种并发控制的方法;乐观锁( Optimistic Locking
) 是相对悲观锁而言,乐观锁是假设认为即使在并发环境中,外界对数据的操作一般是不会造成冲突,所以并不会去加锁(所以乐观锁不是一把锁),而是在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回冲突信息,让用户决定如何去做下一步,比如说重试,直至成功为止;数据库的乐观锁,并不是利用数据库本身的锁去实现的,可能是利用某种实现逻辑去实现做到乐观锁的思想
数据库的乐观并发控制要解决的是数据库并发场景下的写-写
冲突,指在用无锁的方式去解决
其实数据库乐观锁的具体实现几乎就跟Java中乐观锁采用的CAS算法思想是一致,所以我们可以从CAS算法中学习到数据库乐观锁的设计:
CAS指令全称为Compare and Swap,它是系统的指令集,整个CAS操作是一个原子操作,是不可分割的。从具体的描述上,我们可以这么看CAS操作:
CAS指令需要3个操作数,分别是内存位置V,旧的预期值A,和新值B。CAS指令执行时,当我们读取的内置位置V的现值等于旧预期值A时,处理器才会将新值B去更新内置位置V的值。否则它就不执行更新,但无论是否更新V的值,都会返回V的旧值。
我们通俗的放到代码层次上去理解i = 2; i++
,就是说:
B = i++ = 3
,新值B就会对内存位置V进行更新,所以内存位置V的值就变成了B的值,3数据库层的乐观锁实现也类似代码层面的实现
通常乐观锁的实现有两种,但它们的内在都是CAS思想的设计:
version
)实现
- 这是乐观锁最常用的一种实现方式。什么是数据版本呢?就是在表中增添一个字段作为该记录的版本标识,比如叫
version
,每次对该记录的写操作都会让version+ 1
。- 所以当我们读取了数据(包括
version
),做出更新,要提交的时候,就会拿取得的version
去跟数据库中的version
比较是否一致,如果一致则代表这个时间段,并没有其他的线程的也修改过这个数据,给予更新,同时version + 1
;如果不一致,则代表在这个时间段,该记录以及被其他线程修改过了, 认为是过期数据,返回冲突信息,让用户决定下一步动作,比如重试(重新读取最新数据,再过更新)update table set num = num + 1 , version = version + 1 where version = #{version} and id = #{id}
timestamp
)实现
- 表中增加一个字段,名称无所谓,比如叫
update_time
, 字段类型使用时间戳(timestamp
)- 原理和方式一一致,也是在更新提交的时检查当前数据库中数据的时间戳和自己更新前取到的时间戳是否一致,如果一致则代表此刻没有冲突,可以提交更新,同时时间戳更新为当前时间,否则就是该时间段有其他线程也更新提交过,返回冲突信息,等待用户下一步动作。
update table set num = num + 1 ,update_time = unix_timestamp(now()) where id = #{id} and update_time = #{updateTime}
但是我们要注意的是,要实现乐观锁的思想的同时,我们必须要要保证CAS多个操作的原子性,即获取数据库数据的版本,拿数据库的数据版本与之前拿到的版本的比较,以及更新数据等这几个操作的执行必须是连贯执行,具有复合操作的原子性;所以如果是数据库的SQL,那么我们就要保证多个SQL操作处于同一个事务中
优点:
在读多写少的并发场景下,可以避免数据库加锁的开销,提高Dao层的响应性能
其实很多情况下,我们orm工具都有带有乐观锁的实现,所以这些方法不一定需要我们人为的去实现
缺点:
在写多读少的并发场景下,即在写操作竞争激烈的情况下,会导致CAS多次重试,冲突频率过高,导致开销比悲观锁更高
什么是MVCC?
MVCC
MVCC
,全称Multi-Version Concurrency Control
,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
mvcc - @百度百科
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
什么是当前读和快照读?
什么是MySQL InnoDB下的当前读
和快照读
?
当前读
像select lock in share mode(共享锁
), select for update ; update, insert ,delete(排他锁
)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
快照读
像不加锁
的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
说白了快照读就是MVCC思想在MySQL的具体非阻塞读功能实现,整个MVCC多并发控制的目的就是为了实现读-写冲突不加锁,提高并发读写性能,而这个读指的就是快照读
, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
对乐观锁和悲观锁的抉择主要体现在写-写
在悲观锁和乐观锁的抉择中,我们可以从下面三个因素来考虑:
所以我们知道:
悲观并发控制(PCC)是一种用来解决读-写冲突
和写-写冲突
的的加锁并发控制, 为每个操作都加锁,同一时间下,只有获得该锁的事务才能有权利对该数据进行操作,没有获得锁的事务只能等待其他事务释放锁;所以可以解决脏读,幻读,不可重复读,第一类更新丢失,第二类更新丢失的问题
乐观并发控制(OCC)是一种用来解决写-写冲突
的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试。乐观并发控制类似自旋锁。乐观并发控制适用于低数据争用,写冲突比较少的环境;无法解决脏读,幻读,不可重复读,但是可以解决更新丢失问题
多版本并发控制(MVCC)是一种用来解决读-写冲突
的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 这样在读操作时就不用阻塞写操作,写操作也不用阻塞读操作;不仅可以提高并发性能,还可以解决脏读,幻读,不可重复读等事务问题。更新丢失问题除外
总的来说,MVCC的出现就是数据库不满用悲观锁去解决读-写冲突问题,因性能不高而提出的解决方案,所以在数据库中,我们可以形成两个组合:
MVCC + 悲观锁
MVCC + 乐观锁
这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题
乐观锁和 MVCC 的区别?- @作者:知乎
乐观锁(Optimistic Locking)与悲观锁(Pessimistic Locking) - @作者:outofmemory
深入理解乐观锁与悲观锁 - @作者:
Optimistic vs. Pessimistic locking - @作者: StackOverFlow
乐观锁与悲观锁各自适用场景是什么? - @作者:知乎
乐观锁与CAS,MVCC - @作者:shuff1e
悲观锁,乐观锁以及MVCC - @作者:wezheng
【数据库】悲观锁与乐观锁与MySQL的MVCC实现简述 - @作者:Nick Huang
如果觉得对你有帮助,能否点个赞或关个注,以示鼓励笔者呢?!