mysql乐观锁和悲观锁详解

mysql乐观锁和悲观锁详解

相信不少朋友在面试的时候,都会被问到乐观锁和悲观锁的问题,若是不清楚其概念和用法的状况下,相信不少朋友都会感受很懵逼,那么面试的结果也就不言而喻了。
那么乐观锁和悲观锁究竟是个什么东西,用它能来作什么呢?
                相信你们都遇到这种场景,当不少人(一两我的估计不行)同时对同一条数据作修改的时候,那么数据的最终结果是怎样的呢?
这也就是咱们说的并发状况,这样会致使如下两种结果:html

  1. 更新错误,你修改以后的数据可能被别人覆盖了,致使你很懵逼,甚至怀疑本身开发的功能是否有问题;
  2. 脏读,数据更新错误,致使读数据也是错的,查询出一些默认奇妙的数据,看到的不是你本身修改的结果。

这样的问题怎么解决呢?因而乎,锁就这样产生了,锁分为乐观锁和悲观锁,它的目的是用来解决并发控制的问题。
MyISAM引擎不支持事务,因此不考虑它有乐观锁和悲观锁概念。MyISAM只有表锁,锁又分为读锁和写锁。在这里咱们只讨论InnoDB引擎。
须要注意的是,乐观锁和悲观锁并非解决并发控制的惟一手段(也可使用消息中间件kafka,MQ之类的做为缓冲等等),并且乐观锁和悲观锁并不只限制在mysql中使用,它是一种概念,不少其余的应用,如redis,memcached等,只要存在并发状况的,均可以应用这种概念,只是方式上有些差异而已。mysql


1、乐观锁
乐观锁,简单地说,就是从应用系统层面上作并发控制,去加锁。
实现乐观锁常见的方式:版本号version
实现方式,在数据表中增长版本号字段,每次对一条数据作更新以前,先查出该条数据的版本号,每次更新数据都会对版本号进行更新。在更新时,把以前查出的版本号跟库中数据的版本号进行比对,若是相同,则说明该条数据没有被修改过,执行更新。若是比对的结果是不一致的,则说明该条数据已经被其余人修改过了,则不更新,客户端进行相应的操做提醒。
使用版本号实现乐观锁
使用版本号时,能够在数据初始化时指定一个版本号,每次对数据的更新操做都对版本号执行+1操做。并判断当前版本号是否是该数据的最新的版本号。 面试

//1.查询出商品信息 redis

select status,version from t_goods where id=#{id} sql

//2.根据商品信息生成订单 数据库

//3.修改商品status为2 session

update t_goods 并发

set status=2,version=version+1 memcached

where id=#{id} and version=#{version};
性能

注意第二个事务执行update时,第一个事务已经提交了,因此第二个事务可以读取到第一个事务修改的version。

下面这种极端的状况:

咱们知道MySQL数据库引擎InnoDB,事务的隔离级别是Repeatable Read,所以是不会出现脏读、不可重复读。
在这种极端状况下,第二个事务的update因为不能读取第一个事务未提交的数据(第一个事务已经对这一条数据加了排他锁,第二个事务须要等待获取锁),第二个事务获取了排他锁后,会发现version已经发生了改变从而提交失败。

2、悲观锁
悲观锁,简单地说,就是从数据库层面上作并发控制,去加锁。
悲观锁的实现方式有两种:共享锁(读锁)排它锁(写锁)
共享锁(IS锁),实现方式是在sql后加LOCK IN SHARE MODE,好比SELECT ... LOCK IN SHARE MODE,即在符合条件的rows上都加了共享锁,这样的话,其余session能够读取这些记录,也能够继续添加IS锁,可是没法修改这些记录直到你这个加锁的session执行完成(不然直接锁等待超时)。
排它锁(IX锁),实现方式是在sql后加FOR UPDATE,好比SELECT ... FOR UPDATE ,即在符合条件的rows上都加了排它锁,其余session也就没法在这些记录上添加任何的S锁或X锁。若是不存在一致性非锁定读的话,那么其余session是没法读取和修改这些记录的,可是innodb有非锁定读(快照读并不须要加锁),for update以后并不会阻塞其余session的快照读取操做,除了select ...lock in share mode和select ... for update这种显示加锁的查询操做。

经过对比,发现for update的加锁方式无非是比lock in share mode的方式多阻塞了select...lock in share mode的查询方式,并不会阻塞快照读

mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型。

在Java中,synchronized的思想也是悲观锁。

以排它锁为例

要使用悲观锁,咱们必须关闭mysql数据库的自动提交属性,由于MySQL默认使用auto commit模式,也就是说,当你执行一个更新操做后,MySQL会马上将结果进行提交。set autocommit=0; 

//0.开始事务 

begin;/begin work;/start transaction; (三者选一就能够) 

//1.查询出商品信息 

select status from t_goods where id=1 for update; 

//2.根据商品信息生成订单 

insert into t_orders (id,goods_id) values (null,1); 

//3.修改商品status为2 

update t_goods set status=2; 

//4.提交事务 

commit;/commit work;

上面的查询语句中,咱们使用了select…for update的方式, 这样就经过开启排他锁的方式实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被咱们锁定了,其它的事务必须等本次事务提交以后才能执行。这样咱们能够保证当前的数据不会被其它事务修改。

补充:
1.MyISAM在执行查询语句(SELECT)前,会自动给涉及的全部表加读锁,在执行更新操做 (UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁。
2.MySQL InnoDB默认行级锁。 行级锁都是基于索引的,若是一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住。
3.从上面对两种锁的介绍,咱们知道两种锁各有优缺点,不可认为一种好于另外一种,像乐观锁适用于写比较少的状况下(多读场景),即冲突真的不多发生的时候,这样能够省去了锁的开销,加大了系统的整个吞吐量。但若是是多写的状况,通常会常常产生冲突,这就会致使上层应用会不断的进行retry,这样反却是下降了性能,因此通常多写的场景下用悲观锁就比较合适。

参考:http://www.cnblogs.com/exceptioneye/p/5373477.html

相关文章
相关标签/搜索