分布式锁之数据库锁

使用数据锁:悲观锁 或者 乐观锁

  1. 一个sql:直接更新时判断,在更新中判断库存是否大于0

    update table set surplus = (surplus - buyQuantity) where id = 1 and (surplus - buyQuantity) > 0 ;

  2. 悲观锁:在读取数据时锁住那几行,其他对这几行的更新需要等到悲观锁结束时才能继续 。

    select … for update

  3. 乐观锁:读取数据时不锁,更新时检查是否数据已经被更新过,如果是则取消当前更新进行重试。

    version 或者 时间戳(CAS思想)。

悲观锁

在MySQL的InnoDB中,预设的Tansaction isolation level 为REPEATABLE READ(可重读)
在SELECT 的读取锁定主要分为两种方式:

  • SELECT … LOCK IN SHARE MODE (共享锁)
  • SELECT … FOR UPDATE (悲观锁)
  • 这两种方式在事务(Transaction) 进行当中SELECT 到同一个数据表时,都必须等待其它事务数据被提交(Commit)后才会执行。
  • 而主要的不同在于LOCK IN SHARE MODE 在有一方事务要Update 同一个表单时很容易造成死锁。
  • 简单的说,如果SELECT 后面若要UPDATE 同一个表单,最好使用SELECT … FOR UPDATE。

代码实现

改造StockService:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在StockeMapper中定义selectStockForUpdate方法:

public interface StockMapper extends BaseMapper<Stock> {

    public Stock selectStockForUpdate(Long id);
}

在StockMapper.xml中定义对应的配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.distributedlock.mapper.StockMapper">

    <select id="selectStockForUpdate" resultType="com.atguigu.distributedlock.pojo.Stock">
        select * from db_stock where id = #{id} for update
    </select>
</mapper>

压力测试

注意:测试之前,需要把库存量改成5000。压测数据如下:比jvm性能高很多,比无锁要低将近1倍
在这里插入图片描述

mysql数据库存:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

乐观锁

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则重试。那么我们如何实现乐观锁呢

使用数据版本(Version)记录机制实现,这是乐观锁最常用的实现 方式。一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录 的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新。

给db_stock表添加version字段:

在这里插入图片描述

对应也需要给Stock实体类添加version属性。此处略。。。。

代码实现

public void checkAndLock() {

    // 先查询库存是否充足
    Stock stock = this.stockMapper.selectById(1L);

    // 再减库存
    if (stock != null && stock.getCount() > 0){
        // 获取版本号
        Long version = stock.getVersion();

        stock.setCount(stock.getCount() - 1);
        // 每次更新 版本号 + 1
        stock.setVersion(stock.getVersion() + 1);
        // 更新之前先判断是否是之前查询的那个版本,如果不是重试
        if (this.stockMapper.update(stock, new UpdateWrapper<Stock>().eq("id", stock.getId()).eq("version", version)) == 0) {
            checkAndLock();
        }
    }
}

重启后使用jmeter压力测试工具结果如下:

在这里插入图片描述

修改测试参数如下:

在这里插入图片描述

测试结果如下:

在这里插入图片描述

说明乐观锁在并发量越大的情况下,性能越低(因为需要大量的重试);并发量越小,性能越高。

mysql锁总结

性能:一个sql > 悲观锁 > jvm锁 > 乐观锁

如果追求极致性能、业务场景简单并且不需要记录数据前后变化的情况下。

​ 优先选择:一个sql

如果写并发量较低(多读),争抢不是很激烈的情况下优先选择:乐观锁

如果写并发量较高,一般会经常冲突,此时选择乐观锁的话,会导致业务代码不间断的重试。

​ 优先选择:mysql悲观锁

不推荐jvm本地锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值