理解Mybatis的缓存机制
最近在解决性能问题的时候,在与spring btach中的某个tasklet分页查询数据并进行数据的处理的时候,大概压了40w的数据,当然在结果集中解析出来的数据对象更大,结果导致了OOM问题,用内存分析工具MemoryAnalyzer查看到了大对象的内存占用指向了mybatis查询出来的对象,然后就去跟踪调用,结果跟踪到了mybatis的CacheExecutor,于是想到可能是mybatis的缓存,但是之前没有对它进行详细的了解,所以就掉坑里了,没有仔细的思考,很轻易的跳过了这种可能性.
Mybatis的缓存按照作用域划分可以分为,
一级缓存: **sqlsession** 级别的缓存
二级缓存: **namespace**级别的缓存
一级缓存
- 概要
Mybatis默认开启了一级缓存,一级缓存有两个级别:一个是session,默认是session级别的,即在一个sqlsession中执行的所有语句都会共享一个缓存. 一种是statement级别,即缓存只对当前的statement有效,换句话说就是如果指定为statement级别相当于关闭了一级缓存.
有个坑要说一下:
使用mybatis-spring时: 每次查询spring会重新创建sqlsession,所以一级缓存是不生效的.
但是当开启事务时,spring会使用同一个sqlsession查询,这种情况下一级缓存时生效的.
- 原理
在一级缓存中,当sqlSession执行写入更新,删除,则会清除sqlSession中的一级缓存.
- CachingExecutor源码
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
二级缓存
- 概要
二级缓存是基于namespace的,也就是全局的,默认是关闭的.
如果多个sqlsession之间需要共享缓存,则需要使用到二级缓存.开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前, 先在CachingExecutor进行二级缓存的查询. - 原理
二级缓存开启后,同一个namespace下的所有操作语句,都影响这同一个cache,即二级缓存被多个sqlsession共享,是一个全局的变量.
- type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
- eviction: 定义回收的策略,常见的有FIFO,LRU。
- flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
- size: 最多缓存对象的个数。
- readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
- blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
- cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache
- 源码分析
- 总结
- MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
- MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。