乐观锁与悲观锁

在数据库中,悲观锁的流程如下:

  在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。

  如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常

  如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁。

  其间如果有其他对该记录做修改或加排他锁的操作,都会等待解锁或直接抛出异常。

MySQL InnoDB中使用悲观锁

   悲观锁,指的是对数据被外界修改持保守(悲观)态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现往往依靠数据库提供的锁机制,也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据。悲观并发控制主要用于数据争用激烈的环境以及发生并发冲突时,使用锁保护数据的成本要低于回滚事务的成本的环境中。和乐观锁相比,悲观锁则是一把真正的锁,它通过SQL语句“select for update”锁住select出的那批数据,这时如果其他事务来更新这批数据时会等待。

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

      要使用悲观锁,必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当执行一个更新操作后,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的 那条数据就被锁定,其它的事务必须等本次事务提交之后才能执行。这样可以保证当前的数据不会被其它事务修改。

  需要注意的是,在事务中,只有SELECT ... FOR UPDATELOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT ... 则不受此影响。拿上面的实例来说,当执行select status from t_goods where id=1 for update;后。在另外的事务中如果再次执行select status from t_goods where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果是在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不会受第一个事务的影响。

  使用 select…for update 会把数据给锁住,不过需要注意一些锁的级别,MySQL InnoDB默认行级锁行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

优点与不足

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

乐观锁

  乐观锁假设认为数据一般情况下,不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突,则让返回用户错误的信息,让用户决定如何去做。或者就是每次不加锁去执行某项操作,如果发生冲突则失败并重试,直到成功为止,其实本质上不算锁,所以很多地方也称之为 自旋。

  相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本

  数据版本为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

  实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳

  使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号。

1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};

  MySql对于事务之间并发控制的实现并不是简单的使用行级锁。MySql在读操作时并不加锁,只有在写操作时才会对修改的资源加锁。

       

参考:

http://chenzhou123520.iteye.com/blog/1860954(强烈推荐)

http://chenzhou123520.iteye.com/blog/1863407(强烈推荐)

posted on 2018-10-28 17:44  溪水静幽  阅读(131)  评论(0)    收藏  举报