数据库的悲观锁、乐观锁


 

并发控制

并发情况下,需要做一些控制(加锁),保证共享数据的一致性。

并发操作数据库时,需要给数据库中的数据加锁,确保数据库中数据的一致性。

 

数据库锁的常见分类

  • 按使用方式来分:悲观锁、乐观锁
  • 按锁级别来分:共享锁、排它锁
  • 按锁粒度来分:行级锁、表级锁、页级锁,页介于行、表之间

 

悲观锁  Pessimistic Lock

假设是最坏的情况,认为其它线程一定会修改当前线程使用的数据库数据,当前线程一定要给使用的数据库数据加锁。

悲观锁只是个统称,并不是指某一种具体的锁。悲观锁主要包括

  • 共享锁(S锁,share),又称为读锁,所有线程都可以访问,但都只能读、不能写
  • 排它锁(X锁),又称为写锁,是排它的,同一时刻只能有一个线程读写这部分数据

Java中的synchronized、ReentrantLock等独占锁就是悲观锁思想的实现。
 

悲观锁一般要借助数据库本身提供的锁机制来实现,以mysql的InnoDB引擎为例:加排它锁

begin;  --开始事务

select * from tb_user where id=1 for update;  --先给要使用的行加锁
update tb_user set username='chy',password='abcd' where id=1;  --修改数据

commit;  --提交事务

先用 select…for update 锁住要使用的行,再修改数据。

InnoDB默认使用行级锁,但行级锁是基于索引的,如果sql语句用不到索引,会使用表级锁将整张表锁住。

 

乐观锁  Optimistic Lock

假设是很好的情况,认为其它线程一般不会修改当前线程使用的数据库数据,使用时不加锁,只在提交更新时检查数据是否被其它线程修改过。

乐观锁常见的实现方式有2种:CAS、版本号。
 

CAS
Compare and Swap,提交时先和之前从数据库查到的数据比较,数据没有被其它线程修改才和数据库交换数据(提交修改)。

select goods_quantity from tb_goods where goods_id=1;  //先查该种商品的库存,假设为100
update tb_goods set goods_quantity=goods_quantity-1 where goods_id=1 and goods_quantity=100;  //提交修改时带上条件库存等于100,确保数据没有被修改

CAS方式可能会发生ABA问题:

之前查到库存为100(A),期间某个线程修改了数据,比如售出1件,库存改为99(B),之后又把修改了库存为原来的值100(A),比如买家取消订单。

用来标识的数据和原来的相同,但记录其实已被修改过了,修改的可能是其它字段。

 

版本号机制(推荐)

设计表时增加version列,每次更新记录时都将该条记录的version+1。

执行更新操作时先查询这要使用记录的version,提交更新时比较version前后是否一致,一致就说明数据未被其它线程修改;不一致说明数据已被其它线程修改,重试指定次数(重试需要自己写代码实现)。

--不加锁
select version from tb_goods where goods_id=1;  --先查询这条记录的数据版本号,假设为5
update tb_goods set goods_quantity=goods_quantity-1,version=version+1 where goods_id=1 and version=5;  --提交更新时检测版本号是否一致,如果一致,执行时还需要将版本号+1。写操作会返回受影响的记录数,根据返回值判断操作是否成功

不一定要用版本号,时间戳也行

 

乐观锁的优化写法

如果数据库操作只有一条sql语句,不涉及事务

update tb_goods set goods_quantity=goods_quantity-1 where goods_id=1 and goods_quantity-1>=0;  --不用管其它线程是否修改了数据(库存),只要库存够就行

 

悲观锁、乐观锁的比较、选择

悲观锁是每次操作都要加锁,乐观锁实际上并没有加锁。

悲观锁是假设其它线程一定会修改当前线程使用的数据,对应高并发场景;乐观锁是假设其它线程一般不会修改当前线程使用的数据,对应低并发场景。但实际使用时,高并发一般使用乐观锁。
 

悲观锁、悲观锁都保证了数据一致性,乐观锁的版本号如果对不上,也不会提交修改。

高并发时,悲观锁每次操作都要加锁,更新成功概率大,但严重拉低性能,频繁使用锁还可能发生死锁;乐观锁虽然更新成功概率低一些,但每次都不加锁,性能高、能扛住高并发。面对高并发,首先要能扛住,如果扛都扛不住,很多请求都是拒绝连接,谈何性能。
 

并发量小的项目,悲观锁、乐观锁的更新成功率都高,但悲观锁加了锁,更新成功率更高,优先使用悲观锁;并发量大的项目,首先保证扛得住,优先使用乐观锁。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值