业务背景:
一个接口,后端需要将参数分组多线程去请求第三方数据,然后拿到返回数据拼起来入库
数据库使用的是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);
}
好咯,问题解决了。撒花撒花~ ~