关于并发高的时候,数据重复insert的问题

uncle_zhang 发布于 2018/03/19 12:03
阅读 2K+
收藏 5

大家好,我有一个业务大概是这样,两类参数,一个可以理解为user key(手机号码,邮箱之类的),另外一个是业务数据。逻辑大概是
进入这个业务时,先判断user id是否存在,不存在就新增一个用户,存在就根据这个key获取对应的用户;然后把业务数据和用户绑定起来(在另外一张表)
现在有个问题,如果并发多的情况下,比如两个请求,userkey是一样的,但是需要绑定的业务数据不一样,同时进入这个方法,开了两个数事务,
如果这个key对应的用户不存在,两个事务都会insert一个用户,那么必定有一个insert会报重复key的错误。
请问这种情况怎么取避免?

加载中
0
一只小桃子
一只小桃子

简单来说是synchronized,如果认为有性能问题,可以使用redis实现一个分布式锁,每个用户一个锁。用队列是自找麻烦

kipeng300
kipeng300
如果是集群部署呢?synchronized还是不能解决 如果在分布式系统或者集群部署还是推荐使用MQ或者全局锁来解决
2
蓝水晶飞机
蓝水晶飞机

扯淡的 MQ 方案、MQ 消息就一定是一个个去吃的吗,如果是做分布式那么可以用 MQ,但仅仅是为了这么一个简单的需求而用 MQ ,那为何不去使用 SingleThreadPool 呢(或者锁就可以了),让一个线程去给你执行、无网络传输性能还更好。

MQ 是用来解耦、将耗时的、不需要实时返回的业务丢到异步去处理、失败了可以重试、监控。

楼主的业务应该是要立即执行完毕返回的。

来自还没有机会搞分布式的蓝水晶飞机。

1
太黑_thj
太黑_thj

数据安全与并发,这两者就像鱼和熊掌;你可以关键地方加锁,部分加锁对系统性能影响相对会小一些,比如你这个问题,可以在获取用户和添加用户的地方加锁

蓝水晶飞机
蓝水晶飞机
回复 @蓝水晶飞机 : 回复错了,要回复 @uncle_zhang
蓝水晶飞机
蓝水晶飞机
回复 @太黑_thj : 你敢整个系统都用 synchronized(Object.class) { 耗时业务代码 }我就服你哈哈哈。
太黑_thj
太黑_thj
@uncle_zhang 回复@uncle_zhang : 找个单例对象来做锁
uncle_zhang
uncle_zhang
如果多个实例,锁就没用了吧
0
乌龟壳
乌龟壳

报错的那个再获取一次key不就好了

0
蓝水晶飞机
蓝水晶飞机

加一个 cache ,数据有效期10分钟。

请求开始:

synchronized (XxxService.class) {

    boolean duplicate  = cache.get(userKey ) != null || findDBDataByUserKey(userKey) != null ; // 缓存里面有没有,如果有则说明有发生重复(先查缓存后查数据库,缓存查不到重复就查数据库)

    if 发生重复 抛个异常或者return ...

    else 则

        cache.put(userKey) 

    }

// 业务处理 ...

0
大后锋
大后锋

用消息队列啊。

0
唱不完的离歌
唱不完的离歌

加锁。(会影响并发性能)

用消息队列。(很nice)

唱不完的离歌
唱不完的离歌
回复 @uncle_zhang : mq目前来说单机1w多的的qps没啥问题。至于说再大的话我也不清楚有没有更好的选择。等待赐教。
uncle_zhang
uncle_zhang
目前想到的办法是消息队列,但是貌似只能单个消费者才能解决这个问题。如果某段时间访问太多,对MQ的压力也会太大
0
书香缘
书香缘

建议使用redis数据库的队列

0
郭里奥

建议从业务逻辑上判断是不是可以同一个id拥有多个业务。再进行操作

0
颖辉小居
颖辉小居

方案一:把执行sql的那部分代码提出来,包括先查询,没有则新增,有则返回的逻辑。将这些代码单独放到一个私有函数中,你说的两个开启事物的业务方法都调用这个私有函数。然后在这个私有方法加锁。

方案二:两边都trycatch 如果报你说的那个insert时发现主键重复,就在catch里再查一次后绑定。

方案三(推荐):消息队列

uncle_zhang
uncle_zhang
用消息队列,消费者应该只能是一个,多个消费者和并发一样。这样的话,如果某段时间访问太多,对MQ的压力会不会太大?
返回顶部
顶部