笔者这段时间在做第三方用户接入的时候碰到了一个问题:由于自身的系统是在第三方发送请求的时候直接将第三方的账号数据存入数据库的,所以当页面出现多个请求并发执行的时候,会出现用户数据重复插入的问题,之后笔者尝试了几种方式最终解决了这个问题,在此记录一下。
一、单台服务器的处理方案
这种情况是最简单的一种情况,笔者的处理方法是给插入数据的代码块加锁,这样就能保证同一时间只能有一个线程访问该段代码快,这样只要在代码块的开头加上有无相同数据的判断,就能保证数据不会重复插入。伪代码如下:
synchronized(this) {
//查询有无重复数据的方法
User user = userMapper.selectOne(userId);
if (null == user) {
//如果没有查到数据则插入
userMapper.insert(newUser);
}
}
当然加锁的方式各有不同,也可以选择使用Lock来进行手动上锁,或者把相关代码独立出来放到同一个方法中,然后给方法加锁,如下:
private synchronized void addUserInfo() {
//相关的判断和插入方法
}
当然上诉加锁的方法只对单台服务器有效,如果涉及多台服务器,那么这种方法仍旧不能保证数据库中用户数据的唯一性,下面来介绍第二种多台服务器环境下的处理方案。
二、多台服务器环境下的处理方案
一开始笔者是想用乐观锁来实现,每次插入前先对比版本号,如果版本号不一致则表明有其他线程操作过,就不进行插入了,但是没过多久就发现问题了,现在遇到的情景是新增而不是更新,无法事先就从数据库中拿到将要新增的数据的版本号,所以这个方法被舍弃了。后面笔者想用分布式锁,但是发现现有环境中根本没有使用到redis来进行缓存,根本无法实现分布式锁。进而又衍生出了笔者现在所使用的方法,通过索引冲突来解决问题
索引冲突法:这个方法可以说是当前情景下最简单的方法了,只需要把用户表的用户编号字段设置成唯一索引(主键和唯一索引不会冲突哦),然后在插入数据的Java代码中捕获唯一索引异常但不对其做任何处理就可以了。下面有设置唯一索引的语句,和异常捕获的代码实现:
设置唯一索引的sql语句
ALTER TABLE `table_name` ADD UNIQUE (`column`)
异常捕获的代码
try {
userMapper.insert(newUser);
} catch (DataIntegrityViolationException e) {
//唯一索引异常进入该段代码块
}
当然,笔者还是推荐使用分布式锁的,因为这样能减少异常的发生,从而避免因为异常而可能引发的一些问题,但是笔者当下使用的环境不允许,所以只能将就着使用这个方法了。