数据并发的三种场景乐观锁和悲观锁的澄清

乐观锁和悲观锁是一种锁的设计思想,它不是一种具体的锁,它可以有很多具体的实现类。

悲观锁

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法; 悲观锁指的是采用一种持悲观消极的态度,默认数据被外界访问时,必然会产生冲突,所以在数据处理的整个过程中都采用加锁的状态,保证同一时间,只有一个线程可以访问到数据,实现数据的排他性;通常,数据库的悲观锁是利用数据库本身提供的锁机制去实现的。

数据库的悲观并发控制可以解决读-写冲突和写-写冲突,指在用加锁的方式去解决。

悲观锁的实现

常情况下,数据库的悲观锁就是利用数据库本身提供的锁去实现的

当然数据库提供了非常多的锁,每种数据库提供的锁也不尽然相同,所以具体情况就要看是什么锁了,比如行锁,表锁等

优点与缺点

优点:

适合在写多读少的并发环境中使用,虽然无法维持非常高的性能,但是在乐观锁无法提更好的性能前提下,可以做到数据的安全性

缺点:

加锁会增加系统开销,虽然能保证数据的安全,但数据处理吞吐量低,不适合在读书写少的场合下使用

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

乐观锁

在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法;乐观锁( Optimistic Locking ) 是相对悲观锁而言,乐观锁是假设认为即使在并发环境中,外界对数据的操作一般是不会造成冲突,所以并不会去加锁(所以乐观锁不是一把锁),而是在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回冲突信息,让用户决定如何去做下一步,比如说重试,直至成功为止;数据库的乐观锁,并不是利用数据库本身的锁去实现的,可能是利用某种实现逻辑去实现做到乐观锁的思想

数据库的乐观并发控制要解决的是数据库并发场景下的写-写冲突,指在用无锁的方式去解决

CAS思想

其实数据库乐观锁的具体实现几乎就跟Java中乐观锁采用的CAS算法思想是一致,所以我们可以从CAS算法中学习到数据库乐观锁的设计:

CAS指令全称为Compare and Swap,它是系统的指令集,整个CAS操作是一个原子操作,是不可分割的。从具体的描述上,我们可以这么看CAS操作:

CAS指令需要3个操作数,分别是内存位置V,旧的预期值A,和新值B。CAS指令执行时,当我们读取的内置位置V的现值等于旧预期值A时,处理器才会将新值B去更新内置位置V的值。否则它就不执行更新,但无论是否更新V的值,都会返回V的旧值。

我们通俗的放到代码层次上去理解i = 2; i++,就是说:

数据库层的乐观锁实现也类似代码层面的实现

数据库中乐观锁的实现

通常乐观锁的实现有两种,但它们的内在都是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,全称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多并发控制的目的就是为了实现读-写冲突不加锁,提高并发读写性能,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。

总结乐观锁和悲观锁的抉择

对乐观锁和悲观锁的抉择主要体现在写-写

悲观锁和乐观锁的抉择中,我们可以从下面三个因素来考虑:

所以我们知道:

OCC,PCC,MVCC三者的关系

总的来说,MVCC的出现就是数据库不满用悲观锁去解决读-写冲突问题,因性能不高而提出的解决方案,所以在数据库中,我们可以形成两个组合:

MVCC解决读写冲突,悲观锁解决写写冲突

MVCC解决读写冲突,乐观锁解决写写冲突

这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题。

引自:

限时特惠:本站每日持续更新海量各大内部网赚创业教程,会员可以下载全站资源点击查看详情
站长微信:11082411

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。