今天去面试了,面试官问了个问题,高并发情况下,怎么防止库存超卖?
回去之后搜了一下解决方法,其中的一种解决方法就是通过给数据库加锁,也可以防止库存超卖的情况
数据的锁定分为两种,第一种叫作悲观锁,第二种叫作乐观锁。
1、悲观锁,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住。【数据锁定:数据将暂时不会得到修改】
2、乐观锁,认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让用户返回错误的信息。让用户决定如何去做。
理解:
- 乐观锁是一种思想,具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。 之所以叫乐观,因为这个模式没有从数据库加锁。
- 2. 悲观锁是读取的时候为后面的更新加锁,之后再来的读操作都会等待。这种是数据库锁 乐观锁优点程序实现,不会存在死锁等问题。他的适用场景也相对乐观。阻止不了除了程序之外的数据库操作。 悲观锁是数据库实现,他阻止一切数据库操作。
再来说更新数据丢失,所有的读锁都是为了保持数据一致性。 乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户从新操作。 悲观锁则会等待前一个更新完成。这也是区别。
具体业务具体分析 实现:
一、悲观锁
代码语言:javascript复制 1、排它锁,当事务在操作数据时把这部分数据进行锁定,直到操作完毕后再解锁,
其他事务操作才可操作该部分数据。这将防止其他进程读取或修改表中的数据。
代码语言:javascript复制 2、实现:大多数情况下依靠数据库的锁机制或者JAVA代码中的锁实现
二、乐观锁
代码语言:javascript复制 1、如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户重新操作。
代码语言:javascript复制 2、实现:大多数基于数据版本(Version)记录机制实现
当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加
一。
当我们提交更新的时候,判断当前版本信息与第一次取出来的版本值大小,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据,拒绝更新,让用户重新操作。
乐观锁的具体实现模式:
我们知道大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。redis中可以使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。 注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec,discard,unwatch命令都会清除连接中的所有监视。
Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI 和 EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。Redis的事务是下面redis的4个方法来实现 : jedis = RedisUtil.getInstance().getJedis();
代码语言:javascript复制1).multi,开启Redis的事务,置客户端为事务态。
2).exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
3).discard,取消事务,置客户端为非事务态。
4).watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务`将被取消。
参考https://www.cnblogs.com/jasonZh/p/9522772.html https://www.jianshu.com/p/06f1bce98451
代码语言:javascript复制我们在高并发使用redis时,我们不止需要采用分布式事务的模式,还需要采用乐观锁保证数据安全。
redis乐观锁的实现一定要伴随着redis事务。
话不多说直接上代码
redisTemplate.watch("key"); // 1
redisTemplate.multi();
redisTemplate.boundValueOps("key").set("" id);
List<Object> list= redisTemplate.exec();
System.out.println(list);
if(list != null ){
//操作成功
System.out.println(id "操作成功");
}else{
//操作失败
System.out.println(id "操作失败");
}