概述
问题业务场景:按日期累加的流水号出现大量重复流水号问题
代码模拟场景:
String sss = TestMapper.getMaxSerialNo("LiuShuiHaoCuoLuan");
for (int i = 0; i < 100; i++) {
int finalI = i;
Thread a = new Thread(() -> {
String str = businessSerialNoService.getMaxSerialNo("ImTest");
System.out.println(Thread.currentThread().getName() + "......." + finalI + ":" + str);
try {
//睡眠的作用让cpu进行线程调度切换
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
i.start()
}
<insert id="getMaxSerialNo">
<selectKey keyProperty="data.num" resultType="int" order="AFTER">
SELECT S.NUM FROM TEST_NUMS WHERE S.TYPE = #{data.type,jdbcType=VARCHAR}
AND S.TEST_DATE = #{data.testDate,jdbcType=VARCHAR};
</selectKey>
insert into TEST_NUM(TYPE , TEST_DATE, NUM)
values (#{data.type,jdbcType=VARCHAR}, #{data.testDate,jdbcType=VARCHAR},
#{data.num,jdbcType=DECIMAL})
ON DUPLICATE KEY UPDATE NUM = NUM + 1;
</insert>
本地单机测试都出现高概率重复(30%)的现象,问题很严重,跟进解决。贴出一小段日志供参考
2021-08-17 17:39:16.606 DEBUG [Thread-238] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - ==> Parameters: ImTest(String), 20210817(String)
2021-08-17 17:39:16.607 DEBUG [Thread-249] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Updates: 2
2021-08-17 17:39:16.607 DEBUG [Thread-242] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Total: 1
2021-08-17 17:39:16.609 DEBUG [Thread-249] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - ==> Preparing: SELECT S.NUM FROM C_BUSINESS_NUM S WHERE S.BUSINESS_TYPE = ? AND S.BUSINESS_DATE = ?;
2021-08-17 17:39:16.607 DEBUG [Thread-236] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Total: 1
2021-08-17 17:39:16.609 DEBUG [Thread-235] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Total: 1
2021-08-17 17:39:16.609 DEBUG [Thread-241] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Total: 1
2021-08-17 17:39:16.610 DEBUG [Thread-249] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - ==> Parameters: ImTest(String), 20210817(String)
2021-08-17 17:39:16.609 DEBUG [Thread-245] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - ==> Parameters: ImTest(String), 20210817(String), 1(Integer)
2021-08-17 17:39:16.610 DEBUG [Thread-253] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - ==> Preparing: insert into C_BUSINESS_NUM(BUSINESS_TYPE, BUSINESS_DATE, NUM) values (?, ?, ?) ON DUPLICATE KEY UPDATE NUM = NUM + 1;
2021-08-17 17:39:16.611 DEBUG [Thread-253] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - ==> Parameters: ImTest(String), 20210817(String), 1(Integer)
2021-08-17 17:39:16.612 DEBUG [Thread-238] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Total: 1
2021-08-17 17:39:16.612 DEBUG [Thread-234] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Total: 1
2021-08-17 17:39:16.613 DEBUG [Thread-249] [o.a.i.l.j.BaseJdbcLogger:debug:159][][][] - <== Total: 1
......63:191
......66:196
......61:193
......60:197
......65:197
......59:197
......58:197
......62:197
......73:197
问题定位方向:
-
select 和 update 并非同一个事务,可能是spring的事务设置问题
尝试策略:
增加事务注解,设置默认事务管理器(我这里是双数据源,才设置的),设置为MySQL的默认隔离级别
@Transactional(propagation = Propagation.REQUIRES_NEW, value = “mysqlTransactionManager”, isolation = Isolation.REPEATABLE_READ)
效果:对序列的产生没有影响,但是发现了隐藏bug彩蛋。
隐藏彩蛋: 由于是多数据源,如果不指定Transactional的value,是无法设置隔离级别RR的,也就是说虽然单独增加了注解Transactional,但是默认级别降级到了Oracle RC。这个是之前没发现的,后面再具体研究如何处理多数据源的默认隔离级别的问题。 -
语句语法有问题
尝试策略: 返回修正mysql的语法,包括selectKey 、replace into、for update、乐观锁表锁的使用。此处浪费了大量的时间,应该有4、5个小时。实际是方向走错了
展示下调整的另外一种bug语句,这种重复的几率更高了,100次才生成20个序号,数据库值也错乱了,因为先select再update。<insert id="getNewSerialNo"> <selectKey keyProperty="data.num" resultType="int" order="BEFORE"> SELECT IFNULL((SELECT S.NUM FROM C_BUSINESS_NUM S WHERE S.BUSINESS_DATE = #{data.businessDate,jdbcType=VARCHAR} AND S.BUSINESS_TYPE = #{data.businessType,jdbcType=VARCHAR} )+1,1) AS NUM; </selectKey> REPLACE INTO C_BUSINESS_NUM (business_date,business_type,num) VALUES(#{data.businessDate,jdbcType=VARCHAR},#{data.businessType,jdbcType=VARCHAR} ,#{data.num,jdbcType=DECIMAL}) </insert>
-
MyBatis的一级缓存问题,导致每次的select都取了缓存最新的查询值,理论应该返回同一个值才对
**尝试策略:**犯了一个严重意识上的错误,认为一级缓存不会影响多个事务,最先排查了是否关闭一级缓存,发现Oracle关闭了,MySQL没关闭,Oracle之前也出现过重复数据,所以认为一级缓存不会影响。因为没去测试,导致白白浪费了4个小时,两行代码就能搞定的问题。SqlSessionFactory sqlSessionFactory = sessionFactory.getObject(); if (sqlSessionFactory.getConfiguration() != null) { sqlSessionFactory.getConfiguration().setCacheEnabled(false); sqlSessionFactory.getConfiguration().setLocalCacheScope(LocalCacheScope.STATEMENT); log.info("设置默认SqlSessionFactory的缓存开关为关闭,避免分布式或者单事务内多次查询出现缓存问题!"); }
遗留问题跟进:
- 为什么在Service层增加了 REQUIRES_NEW 的事务处理,RR级别,还会出现不再同一个事务的问题,是否是sqlSession复用了?
- MyBatis一级缓存的逻辑处理流程
最后
以上就是迷你歌曲为你收集整理的并发使用MyBatis selectKey 更新update问题总结的全部内容,希望文章能够帮你解决并发使用MyBatis selectKey 更新update问题总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复