MySql中乐观锁、悲观锁与MVCC


一、事务并发带来的问题

事务并发总结起来有三种场景:

  • 读-读:不存在任何问题,也不需要并发控制
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

二、正确了解乐观锁与悲观锁

悲观锁

悲观锁指的是采用一种持悲观消极的态度,默认数据被外界访问时,必然会产生冲突,所以在数据处理的整个过程中都采用加锁的状态,保证同一时间,只有一个线程可以访问到数据,实现数据的排他性;通常,数据库的悲观锁是利用数据库本身提供的锁机制去实现的。

乐观锁

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

对乐观锁和悲观锁的正确认识

乐观锁和悲观锁并不是一种具体的锁,他们指的是一种控制思想,并不是说某种具体的锁。只能说用某种锁(行锁、表锁)实现了悲观锁。

三、乐观锁与悲观锁的实现

了解乐观锁和悲观锁的实现的时候先了解共享锁和排他锁。

共享锁和排他锁

共享锁
当一个事务持有对象锁的时候允许其他事物来获取共享锁,但是不允许其他事务来获取排他锁。
读锁就是一个共享锁,他允许多个事务来读,但不允许数据在持有共享锁的情况下被其他事务修改。
排他锁
当一行数据持有排他锁的时候不允许其他事务读或者修改被排他锁修饰的数据

悲观锁的实现方式

悲观锁的实现方式是借助mysql自身的锁机制实现的。

  • 外界要访问某条数据,那它就要首先向数据库申请该数据的锁(共享锁或排他锁)
  • 如果获得成功,那它就可以操作该数据,在它操作期间,其他客户端就无法再操作该数据了
  • 如果获得失败,则代表同一时间已有其他客户端获得了该锁,那就必须等待其他客户端释放锁
    优点:
    适合在写多读少的并发环境中使用,虽然无法维持非常高的性能,但是在乐观锁无法提更好的性能前提下,可以做到数据的安全性
    缺点:
    加锁会增加系统开销,虽然能保证数据的安全,但数据处理吞吐量低,不适合在读书写少的场合下使用

乐观锁的实现方式

乐观锁采用不加锁的方式解决并发冲突,要用的是CAS算法。
CAS它是系统的指令集,整个CAS操作是一个原子操作,是不可分割的.
乐观锁的两种实现方式都是爱用的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}

优点:
在读多写少的并发场景下,可以避免数据库加锁的开销,提高Dao层的响应性能
其实很多情况下,我们orm工具都有带有乐观锁的实现,所以这些方法不一定需要我们人为的去实现
缺点:
在写多读少的并发场景下,即在写操作竞争激烈的情况下,会导致CAS多次重试,冲突频率过高,导致开销比悲观锁更高

四、MVCC(多版本并发控制)

快照读与当前读

在了解MVCC之前首先要知道什么是快照读什么是当前读

  • 快照读
    指的是一种不加锁的读,普通的select操作就是快照读。快照读的前提是隔离级别不是串行级别,串行级别下的快照就会退化成当前读。快照读的出现是为了基于提高并发性能考虑到,他不需要加锁,大大降低了开销。不过既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

  • 当前读
    当前读并不是狭义上的“读”操作。像select lock inshare mode (共享锁),select for update; update; insert; delete (排他锁)。这些都是当前读。之所以叫当前读因为他保证读取的数据是最新记录。

说白了 MVCC 就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。

乐观锁、悲观锁、MVCC比较

  • **悲观锁:**可以解决读-写冲突,写-写冲突。读和写都会加锁,可以解决脏读、幻读、不可重复读、数据修改丢失
  • 乐观锁:可以解决写-写冲突的另一种不加锁的方式。乐观并发控制适用于低数据争用,写冲突比较少的环境;无法解决脏读,幻读,不可重复读,但是可以解决更新丢失问题
  • MVCC:是一种用来解决读-写冲突的无锁并发控制。读操作只读该事务开始前的数据库的快照。 这样在读操作时就不用阻塞写操作,写操作也不用阻塞读操作;不仅可以提高并发性能,还可以解决脏读,幻读,不可重复读等事务问题。

五、总结

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

  • MVCC + 悲观锁
    MVCC解决读写冲突,悲观锁解决写写冲突
  • MVCC + 乐观锁
    MVCC解决读写冲突,乐观锁解决写写冲突
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值