数据库乐观锁与悲观锁

悲观锁(Pessimistic Lock):

每次拿数据的时候都会担心会被别人修改(疑心重很悲观),所以每次在拿数据的时候都会上锁。确保自己使用的过程中不会被别人访问,自己使用完后再解锁。

期间需要访问该数据的都会等待。

乐观锁(Optimistic Lock):

每次拿数据的时候都完全不担心会被别人修改(心态好很乐观),所以每次在拿数据的时候都不会上锁。

但是在更新数据的时候去判断该期间是否被别人修改过(使用版本号等机制),期间该数据可以随便被其他人读取。

适合修改少,读多,提高吞吐量,即使出现了少量的冲突,这样也省去了大量的锁的开销。如果经常发生冲突(写数据比较多的情况下),上层应用不不断的retry,这样反而降低了性能,对于这种情况使用悲观锁就更合适。

一、悲观锁应用:

以常用的mysql InnoDB存储引擎为例:
加入商品表items表中有一个字段status(1未下单,2已下单),下单前确保status=1。
假设有一件商品,其id为10000;
如果不使用锁,那么操作方法如下:
//查出商品状态: select status from items where id=10000;
//根据商品信息生成订单:insert into orders(id,item_id) values(null,10000);/
/修改商品状态为2:update Items set status=2 where id=10000;

高并发下可能出现问题:

先一步修改为2了,不知道被修改了,下单2次,数据不一致。
查询出信息后就锁定,直到修改完后再解锁。
注:要使用悲观锁,我们必须关闭mysql自动提交属性(默认autocommit模式)
set autocommit=0;(关闭)
//开始事务: begin;/begin work;/start transaction; (三者选一就可以)
同上。。。(select,insert,update)
//提交事务: commit;/commit work;

注:当我执行select status from items where id=10000 for update;后。
我在另外的事务中如果再次执行select status from items where id=10000 for update;
则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,查询不会。

使用select…for update会把数据给锁住,需要注意锁的级别:
MySQL InnoDB默认Row-Level Lock,MySQL 只有明确地指定主键,才会执行Row lock (只锁住被选取的数据) ,
否则MySQL 将会执行Table Lock 。除了主键外,使用索引也会影响数据库的锁定级别。

二、乐观锁应用:

1.使用数据版本(Version)记录机制实现,即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。
当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值+1。
表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。用下面的一张图来说明:
在这里插入图片描述
2.乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

以mysql InnoDB存储引擎为例,还是拿之前的例子商品表items表中有一个字段status,status=1表示该商品未被下单,status=2表示该商品已经被下单,那么我们对每个商品下单前必须确保此商品的status=1。
假设有一件商品,其id为10000;

下单操作包括3步骤:
select (status,version) from items where id=#{id}
根据商品信息生成订单

update items set status=2,version=version+1 where id=#{id} and version=#{version};

为了使用乐观锁,我们需要首先修改items表,增加一个version字段,数据默认version可设为1;

很多产品都有乐观锁的使用,比如分布式存储引擎Tair,Tair中存储的每个数据都有版本号,更新后都会递增,相应的,在Tair put接口中也有此version参数,这个参数是为了解决并发更新同一个数据而设置的,这其实就是乐观锁;

三、MySQL select…for update的Row Lock与Table Lock

举例说明:

数据库表t_goods,包括id,status,name三个字段,id为主键,数据库中记录如下;
在这里插入图片描述
注:为了测试数据库锁,我使用两个console来模拟不同的事务操作,分别用console1、console2来表示。
例1: (明确指定主键,并且有此数据,row lock)
console1:查询出结果,但是把该条数据锁定了
在这里插入图片描述
console2:查询被阻塞
mysql> select * from t_goods where id=1 for update;
console2:如果console1长时间未提交,则会报错
mysql> select * from t_goods where id=1 for update;
ERROR 1205 : Lock wait timeout exceeded; try restarting transaction

例2: (明确指定主键,若查无此数据,无lock)
console1:查询结果为空
mysql> select * from t_goods where id=3 for update;
Empty set
console2:查询结果为空,查询无阻塞,说明console1没有对数据执行锁定
mysql> select * from t_goods where id=3 for update;
Empty set

例3: (无主键,table lock)
console1:查询name=道具 的数据,查询正常
在这里插入图片描述
console2:查询name=装备 的数据,查询阻塞,说明console1把表给锁住了
mysql> select * from t_goods where name=‘装备’ for update;
console2:若console1长时间未提交,则查询返回为空
mysql> select * from t_goods where name=‘装备’ for update;
Query OK, -1 rows affected

例4: (主键不明确,table lock)
console1:查询正常
在这里插入图片描述
console2:查询被阻塞,说明console1把表给锁住了
mysql> select * from t_goods where id>1 for update;
console1:提交事务
mysql> commit;
Query OK, 0 rows affected
console2:console1事务提交后,console2查询结果正常

除了主键外,使用索引也会影响数据库的锁定级别(同上)

转载地址:https://www.jianshu.com/p/8403206e8e8d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值