并发下查询并更新数据重复问题

前言

在日常开发中经常遇到先根据条件判断某条数据是否存在,如果不存在的话就插入,如果存在的话就更新或提示异常。一般代码的模式都是写成下面的这个样子,但是在并发的情况下很容易会重复插入两条数据,大概的情况就是第一个请求进来,没有查询到该用户,通过了if判断,但是if中有比较耗时的逻辑,在第一个请求还没有执行insert的时候第二个请求也进来了,因为这个时候第一个请求还没有执行insert操作,所以第二个请求也没有查询到,该用户也通过了if判断,这个样子就造成了两条重复数据。
// 查询名字叫user1的用户是否存在
UserVo userVo = userMapper.selectUserName("user1");
// 如果不存在就插入数据
if(userVo == null){
	Thread.sleep(10000);
	UserVo userVo = new UserVo();
	userVo.setUserName("user1");
	userMapper.insert(userVo);
}

解决方法:

1.使用synchronized同步代码块

直接将查询校验逻辑和插入逻辑都进行同步,也就是说第一个请求的逻辑没结束,第二个请求就会一直等待着,只有当第一个请求执行完同步代码块中的逻辑释放锁后才第二个请求才能获取到锁执行这段逻辑。
private object obj = new Object();

synchronized (obj) {
	// 查询名字叫user1的用户是否存在
	UserVo userVo = userMapper.selectUserName("user1");
	// 如果不存在就插入数据
	if(userVo == null){
		Thread.sleep(10000);
		UserVo userVo = new UserVo();
		userVo.setUserName("user1");
		userMapper.insert(userVo);
	}
}

2.使用Lock锁

其实和synchronized代码块是相同的作用,但是要注意必须在finally中释放锁,避免出现异常造成死锁。
private Lock lock = new ReetrantLock();
try {
	lock.lock();
	// 查询名字叫user1的用户是否存在
	UserVo userVo = userMapper.selectUserName("user1");
	// 如果不存在就插入数据
	if(userVo == null){
		Thread.sleep(10000);
		UserVo userVo = new UserVo();
		userVo.setUserName("user1");
		userMapper.insert(userVo);
	}
} finally {
	lock.unlock();
}

3.给数据库加唯一索引

既然是要根据用户名称判断是否有重复数据,所以直接在数据库上给userName字段添加UNIQUE索引,这样在第二次重复插入的时候就会提示异常。如果不想重复插入的时候有报错提示可以使用INSERT IGNORE INTO语句,而代码不必做任何逻辑操作。

4.使用redis中的setnx作为锁

redis中的setnx命令是只有当你存入的key不存在时才会成功存入,并返回1,而如果key已经存在的时候则存入失败并返回0,我们可以拿这个特性来当做锁。首先这个方法进来第一步就是这行setnx操作,把查询的用户名存入redis,然后查询该用户时候存在,第一个请求进到if判断中但是还没有执行插入逻辑,第二个请求虽然也没有查询到该用户,但是它的setnx会失败,因为第一个请求的key还没有删除,所以这样就避免了并发重新插入的问题,而且最大的优点是它不像synchronized和Lock无论所有请求进来都只能一个一个通过,使用这种方法是只有当前操作同一个用户有并发请求的时候才会阻塞,而如果请求两个不同的用户时是不会阻塞的,都可以顺利通过,因为存入的key是不同的。
// 自动注入spring的redis操作类
private RedisTemplate redisTemplate;

public String addUser(UserName userName) {
	// 执行setnx命令,存入当前拿来判断的用户名
	BoundValueOperations operations = redisTemplate.boundValueOps(userName);
	// 执行setnx命令的结果,这里封装的方法是直接返回true和false
	boolean addFlag = operations.setIfAbsent(1);
	// 返回结果
	String result = null;

	UserVo userVo = userMapper.selectUserName(userName);
	try {
		if (userVo == null && addFlag == true) {
			Thread.sleep(10000);
			UserVo userVo = new UserVo();
			userVo.setUserName("user1");
			userMapper.insert(userVo);
			result = "更新成功";
		} else {
			result = "更新失败";
		}
	} finally {
		// 无论更新成功和失败都删除setnx添加的key
		operations.getOperations().delete(userName);
	}
	return result;
}	

总结

以上四种方法在单机部署时都可以解决并发操作带来的数据重复问题,在分布式环境下就需要使用分布式锁的方案解决了。
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值