详解悲观锁和乐观锁

背景mysql

考虑下面两个并发带来的问题:sql

一、丢失更新:一个事务的更新结果覆盖了其它事务的更新结果,即所谓的更新丢失。数据库

二、脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。bash

例如:并发

两个用户同时修改商品库存表,A、B同时进入,看到的库存都是100,A购买一件把库存修改成99(100-1)。此时B购买两件把库存修改成98(100-2),由于A、B同时读到的库存都是100,B并不能看到A作的库存更新,因此形成B脏读,形成A丢失更新。性能

因此为了解决这些并发带来的问题。 咱们须要引入并发控制机制--锁。ui

锁分类spa

悲观锁3d

悲观锁就是用户修改数据时看起来很悲观,保守态度,担忧别的用户会同时修改这条数据,因此每次修改时会提早把这条数据锁定起来,只有本身可修改(但别的用户能够读),等本身修改完了再释放锁。code

乐观锁

乐观锁就是用户修改数据时心态很乐观,无论别人修改不修改数据,我都不上锁,我修改的时候判断下数据有没有发生变化,没发生变化我就会更新成功,发生变化了就不会更新成功我再去重试以前的动做直到更新成功。

锁应用

悲观锁

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

关闭命令为:set autocommit=0;

悲观锁通常使用select…for update实现,在执行的时候会锁定数据,虽然会锁定数据,可是不影响其余事务的普通查询使用。

在咱们使用悲观锁的时候事务中的语句例如:

//开始事务

begin;/begin work;/start transaction; (三选一)

//查询信息

select * from order where id=1 for update;

//修改信息

update order set name='names';

//提交事务

commit;/commit work;(二选一)
复制代码

此处的查询语句for update关键字,在事务中只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一条数据时会等待其它事务结束后才执行,通常的SELECT查询则不受影响。

注意事项

执行事务时关键字select…for update会锁定数据,防止其余事务更改数据。可是锁定数据也是有规则的。

查询条件与锁定范围:

  • 一、具体的主键值为查询条件

好比查询条件为主键ID=1等等,若是此条数据存在,则锁定当前行数据,若是不存在,则不锁定。

  • 二、不具体的主键值为查询条件

好比查询条件为主键ID>1等等,此时会锁定整张数据表。

  • 三、查询条件中无主键

会锁定整张数据表。

  • 四、若是查询条件中使用了索引为查询条件

明确指定索引而且查到,则锁定整条数据。若是找不到指定索引数据,则不加锁。

乐观锁

  • 一、使用自增加的整数表示数据版本号,更新时检查版本号是否一致,好比数据库中数据版本为666,更新提交时version=666+1,使用该version值(=667)与数据库version+1(=667)做比较,若是相等,则能够更新,若是不等则有可能其余程序已更新该记录,因此返回错误或者发起重试动做。

例如表

student(id,name,version)

1     a       1
复制代码

当事务一进行更新操做:

update student set name='txt' where id = #{id} and version = #{version};
复制代码

此时操做完后数据会变为id = 1,name = txt,version = 2,当另一个事务二一样执行更新操做的时候,却发现version != 1,此时事务二就会操做失败,从而保证了数据的正确性。

  • 二、使用时间戳来实现,原理同上。

  • 三、使用其余数据库字段,如:金额,更新时添加条件判断金额是否变化,原理同上。

乐观锁图示

结论

两种锁各有优缺点,不能单纯的定义哪一个好于哪一个。乐观锁比较适合数据修改比较少,读取比较频繁的场景,即便出现了少许的冲突,这样也省去了大量的锁的开销,故而提升了系统的吞吐量。可是若是常常发生冲突(写数据比较多的状况下),上层应用不不断的retry,这样反而下降了性能,对于这种状况使用悲观锁就更合适。

相关文章
相关标签/搜索