记录一次多线程更改数据库CLOB字段引发的问题

业务背景:
一个接口,后端需要将参数分组多线程去请求第三方数据,然后拿到返回数据拼起来入库
数据库使用的是oracle

为了保证多线程的数据安全,第一次实现我加入了乐观锁:
V1.0
首先查询出数据,获取当前版本和当前的业务数据,然后组装数据后,通过主键和版本号修改这条记录,修改语句为:

 UPDATE dghy_rc_result
  SET version=version+1 ,RESULT_DATA=#{resultData}
   WHERE result_id = #{resultId} AND version = #{version}

经过测试后发现,会出现偶现问题:
假如一共有三个线程,可能第三个线程的数据会覆盖线程2的数据,导致版本号会少1。

为什么乐观锁没有生效呢?
难道是线程2未修改成功?虽然输出了update返回的count值,确实是1,但是我查了一下 这个count值并不是影响行数,而是匹配的行数,难道就是这个问题导致的? 我立马一顿操作,将返回值设置为影响行数,结果部署后,查看日志,发现count值仍旧是1,但问题依然存在! 修改成功了,那是不是事务没生效呢?
由于是oracle的事务隔离级别是读已提交的,只有可能是线程2的修改的时候事务还未提交,线程3读的还是线程2未修改前的数据。由于代码里面用的是自动提交事务,难道是线程2执行速度比线程3慢?
于是我改良了我的代码

V2.0
先是引入了锁ReentrantLock,线程获取锁后,修改数据并且在手动提交事务后再释放锁。
代码示例:

  lock.lock();
                    try {
                        TransactionStatus transaction = platformTransactionManager.getTransaction(new DefaultTransactionDefinition());
                       //省略了业务代码。。。 
					int count = dghyRcResultMapper.updateVersion(hcret);   
					platformTransactionManager.commit(transaction);

                   }catch (Exception e) {
                        e.printStackTrace();
                        log.error("uuid:{} update Error resultid = {} now:{}",uuid,resultId, DateUtils.format(new Date(), DateUtils.DATE_TIME_MILLION));
                    } finally {
                        lock.unlock();
                        log.info(" uuid:{} unlock Success resultid = {} now:{}", uuid, resultId, DateUtils.format(new Date(), DateUtils.DATE_TIME_MILLION));
                    }

这下既确保了我线程顺序,又确保了事务的提交,肯定没什么问题了,满怀信心的打包部署测试后,发现,依然在!
这是我输出的日志:
在这里插入图片描述
为了验证修改是否成功,我还在update后select一下,并输出日志,发现能查出来版本号已经递增1了,但是另外的线程里,查出来的还是旧的版本号。
这让我整个人都不好了。。。
难道是Mybatis缓存问题?第2个线程读的是缓存而未查库嘛,可是update语句会刷新缓存的鸭。
又是一次头脑风暴。。。。

我把update语句修改了一下,只修改版本号,业务数据不做修改,来验证我的乐观锁是否真的有问题

 UPDATE dghy_rc_result
  SET version=version+1  
   WHERE result_id = #{resultId} AND version = #{version}

发现,问题已经解决了!版本号恢复了自增,并没有被覆盖。
所以,是我数据库RESULT_DATA字段的问题,这是个CLOB类型的大字段,没想到,在这里埋坑!但是没办法鸭,数据量就是很大。

所以考虑换方案来实现这个业务了。

V3.0

既然不让我频繁修改数据库,那我就用countDownLatch来等吧,将多个线程的数据用Vector存起来,一次入库,这样总没问题了吧。

 Vector<JSONObject> vectors = new Vector<JSONObject>();
        final CountDownLatch countDownLatch = new CountDownLatch(threadParam.size());
        log.info("begin check,resultid show :{} threadParam show :{}", resultid, threadParam.toString());
        for (Map<String, String> stringStringMap : threadParam) {
            taskExecutor.execute(() -> {
                hcCheckByThreadService.toHgCheck(stringStringMap, uri, resultid, vectors, countDownLatch);
            });
        }
        countDownLatch.await();
        if (vectors.size() > 0) {
            //处理数据入库
            processDataAndSave(vectors, area, resultid);
        }

好咯,问题解决了。撒花撒花~ ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值