某商品的照片分两种类型:A商品外观照片 和 B商品配件照片两个相册 ,它们保存在同一张picture表中。
在一个事务内按照片类型批量更新商品照片,但操作人只有保存A类型照片的权限,因需要将该商品A照片清空,然后插入新A类照片,然后取商品所有照片,仅发现B类照片,未发现新插入的A类照片。
start transaction; ## 插入id=1数据 INSERT INTO # 当前会话内 查询id=1的数据可见 select * from ? where id='1'; commit ; # 其他事务查询id=1的数据可见 select * from car_picture where id='1';
因此,在同一个事务内,删除数据a,再插入数据b,查询得到的应该是b,但就结果没有拿到b. 导致在同步第三方数据同台时出现少数据的线上问题。
会不会是因为插入行为在另一事务内?
会不会是一级缓存的问题?
每一个sqlsession有自己的Executor,每一个executor有一个local cache.
当用户发起查询时,mybatis会根据当前statement生成一个key,去localcache中查询,如果缓存命中直接返回,未命中,访问db,写入localcache然后返回
信息量:
综上,删除再插入,然后重新获取时不会使用一级缓存。因此不应该是一级缓存的锅。
但事实上在第二次selectList的过程中,发现控制台没有打sqlLog 并且debug到sqlSession.selectList方法上,手动执行前调用sqlSession.clearCache(), 发现获取到了最新数据(不调用clearCache控制台不打sqlLog,取到脏数据),这也就是说缓存还是生效了,尽管对图片表delete和insert过,那么问题在哪?
debug事务内部所有sql操作,查看sqlSession的内存地址
理论上在一个事务内,一个mapper对应开启一个sqlSession。
打印:update和selectList的sqlSession的内存地址
意外发现mybaits-plus在updateBatch的时候和update用的不是同一个sqlSession,这实在太坑了。
/** com.baomidou.mybatisplus.extension.service.impl.ServiceImpl */ public class ServiceImpl, T> implements IService { @Transactional(rollbackFor = Exception.class) @Override public boolean updateBatchById(Collection entityList, int batchSize) { Assert.notEmpty(entityList, "error: entityList must not be empty"); String sqlStatement = sqlStatement(SqlMethod.UPDATE_BY_ID); try (SqlSession batchSqlSession = sqlSessionBatch()) { int i = 0; for (T anEntityList : entityList) { MapperMethod.ParamMap param = new MapperMethod.ParamMap<>(); param.put(Constants.ENTITY, anEntityList); batchSqlSession.update(sqlStatement, param); if (i >= 1 && i % batchSize == 0) { batchSqlSession.flushStatements(); } i++; } batchSqlSession.flushStatements(); } return true; } @Override public boolean updateById(T entity) { return retBool(baseMapper.updateById(entity)); } // 其他 }
如上代码片断,mybatis-plus在updateBatch时的处理逻辑 使用Serivice内部打开的sqlSession ,而普通的updateById则走的mapper更新,mapper更新用的则是另一套session. 这也就是说,
如前文所说,sqlSessionA未监听到update/delete句柄,因此未执行移除缓存的操作,这使得第二次selectList的时候未执行sql语句,直接从缓存中取。
因第3条的缘故,使得一级缓存没有在理想状态下被移除从而引发事故。
至于mybatis-plus为什么selectList和updateBatchBy方法使用了两个不同的sqlSession,感觉是在偷懒,后面可以再另出文章专门探讨。