理解Mybatis的缓存机制

  • Post author:
  • Post category:其他




理解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查询,这种情况下一级缓存时生效的.
  • 原理

:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}

Client SqlSession Executor Cache Mysql Database select query localCache.getObject(key) 返回查询结果 判断查询结果是否为空 1. 如果不为空则直接返回缓存的结果 不会查询数据库 返回查询结果 返回Cache查询结果 2. 如果为空则说明 缓存中没有则需要查询- 数据库 dispatch return database result localCache.putObject 返回Cache结果 返回查询结果 返回查询结果 Client SqlSession Executor Cache Mysql Database

在一级缓存中,当

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进行二级缓存的查询.
  • 原理

:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}

写入

获取

调用方

CachingExecutor

Executor

LocalCache

Mysql

namespace1

namespace2

namespace3

二级缓存开启后,同一个namespace下的所有操作语句,都影响这同一个cache,即二级缓存被多个sqlsession共享,是一个全局的变量.

  • type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
  • eviction: 定义回收的策略,常见的有FIFO,LRU。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
  • cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache
  • 源码分析

    Mybatis-executor相关类图
  • 总结
  1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  3. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。



版权声明:本文为loveuserzzz原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。