乐观锁和悲观锁详解
- 前言
- 乐观锁
- 简介
- 代码实现
- 业务代码
- 悲观锁
- 简介
- 代码实现
- 业务代码
- 总结
前言
数据库中的乐观锁与悲观锁,这是我们在处理并发访问数据库时经常会遇到的问题。通过了解这两种锁的工作原理,我们可以更好地处理并发访问,确保数据的一致性和完整性。
乐观锁
简介
首先,让我们来了解一下什么是乐观锁。**乐观锁的核心思想是假设在大多数情况下,并发访问的数据不会产生冲突。**它采用了一种轻量级的方式,不会阻塞其他的并发操作,而是在进行数据更新时,先获取数据的版本号或者时间戳,然后在更新数据之前再次检查版本号或时间戳是否发生变化。如果变化了,就意味着有其他并发操作修改了数据,那么当前操作就会失败,需要重新尝试。这种方式避免了长时间的锁等待,提高了并发性能。
下面就让我们通过一个具体的例子来说明乐观锁的应用。假设我们有一个在线商城,用户可以同时下单购买商品。在数据库中,每个订单都会有一个版本号字段。当用户下单时,系统会查询当前商品的库存量和价格,并生成一个订单,同时将库存量减少。在这个过程中,我们可以使用乐观锁来避免多个用户同时购买同一件商品的问题。
代码实现
乐观锁的具体实现,我们可以使用mybatis-plus的插件实现,具体步骤如下:
step1:数据表中加version字段,默认值设置为1;
step2:实体类中加version属性,同时属性上面加@Version注解;
step3:添加乐观锁插件。
@Beanpublic MybatisPlusInterceptor interceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//添加分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor());//添加乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
业务代码
package com.qf.service;
import com.qf.entity.TbGoods;
import com.qf.entity.TbOrder;
import com.qf.mapper.TbGoodsMapper;
import com.qf.mapper.TbOrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;@Service
public class OrderService {@Autowiredprivate TbGoodsMapper goodsMapper;@Autowiredprivate TbOrderMapper orderMapper;@Transactionalpublic void buy(String goodsId){//1:查询商品信息 获取乐观锁TbGoods tbGoods = goodsMapper.selectById(goodsId);if (tbGoods == null) {return;}//2:判断库存if(tbGoods.getGoodsStock1() <= 1){return;}//3:下单TbOrder tbOrder = new TbOrder();tbOrder.setOrderId(UUID.randomUUID().toString());tbOrder.setGoodsId(Integer.parseInt(goodsId));tbOrder.setOrderAmount(tbGoods.getGoodsPrice());orderMapper.insert(tbOrder);//4:修改库存tbGoods.setGoodsStock1(tbGoods.getGoodsStock1() - 1);int i = goodsMapper.updateById(tbGoods);if(i == 0){//其他业务影响了数据throw new RuntimeException("数据被人动过!!!");}}
}
悲观锁
简介
接下来,我们再来了解一下什么是悲观锁。悲观锁的实现思想与乐观锁相反,它假设在并发访问中,数据会频繁发生冲突。因此,在操作数据之前,悲观锁会对数据进行加锁,确保其他并发操作无法同时访问该数据。
例如,小明和小红同时购买了同一件商品,系统会生成两个订单。在执行减少库存的操作之前,系统会对该商品的库存进行加锁。这意味着只有一个用户能够成功地执行减少库存的操作,而另一个用户必须等待第一个用户完成操作并释放锁后才能执行。
通过使用悲观锁,我们可以有效避免多个用户同时购买同一件商品导致的库存冲突问题。悲观锁确保了数据的一致性,但也带来了一些性能上的损耗,因为其他用户必须等待锁的释放才能继续操作。
代码实现
@Select("select * from tb_goods where goods_id = #{goodsId} for update")
public TbGoods lock(String goodsId);
业务代码
package com.qf.service;
import com.qf.entity.TbGoods;
import com.qf.entity.TbOrder;
import com.qf.mapper.TbGoodsMapper;
import com.qf.mapper.TbOrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;@Service
public class OrderService {@Autowiredprivate TbGoodsMapper goodsMapper;@Autowiredprivate TbOrderMapper orderMapper;@Transactionalpublic void buy(String goodsId){//获取数据库悲观锁(行锁)goodsMapper.lock(goodsId);//1:查询商品信息TbGoods tbGoods = goodsMapper.selectById(goodsId);if (tbGoods == null) {return;}//2:判断库存if(tbGoods.getGoodsStock1() <= 1){return;}//3:下单TbOrder tbOrder = new TbOrder();tbOrder.setOrderId(UUID.randomUUID().toString());tbOrder.setGoodsId(Integer.parseInt(goodsId));tbOrder.setOrderAmount(tbGoods.getGoodsPrice());orderMapper.insert(tbOrder);//4:修改库存tbGoods.setGoodsStock1(tbGoods.getGoodsStock1() - 1);goodsMapper.updateById(tbGoods);}
}
总结
总的来说,乐观锁和悲观锁都是处理数据库并发访问的重要工具,它们各自具有不同的适用场景和优势。在实际应用中,我们需要综合考虑业务需求、数据一致性要求以及并发性能,选择合适的锁策略来保证系统的稳定性和性能表现。