mybatis一级缓存javalangOutOfMemoryError

18次阅读

共计 2286 个字符,预计需要花费 6 分钟才能阅读完成。

因负责了公司的后端系统,业务人员经常有一些导出数量较大的操作 (百万以上),我们大部分通过成熟的批处理框架解决,但是少不了一些繁琐的配置。故写了一个基于 mybatis 分页一页页的查询写入文件的方式功能,没想到引发了了一场 OutOfMemoryError。现将问题原因记录。模拟此次事故代码如下

public void export(){try (SqlSession session = sqlSessionFactory.openSession()) {OrderMapper mapper = session.getMapper(OrderMapper.class);
         for(int i=0;i<MAX_PAGE;i++){List<Map> list=mapper.query(i*10,10);
             for (Map map : list) {writeToCsv(map)
                 ...
             }
             list=null;
         }
     }catch (Exception e){e.printStackTrace();
     }
 }

在我的机器上本地运行,设置内存相关参数:

-Xms20M -Xmx20M -XX:+PrintGCDetails   -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=e:/

运行几秒中抛出 OutOfMemoryError 异常。
通过打印的堆内存文件分析,org.apache.ibatis.cache.impl.PerpetualCache 占用了 85% 的堆内存,问题的原因肯定时这个 cache 的问题。

通过代码执行,query 代码如下。同一个 sqlSession(也即同一个 BaseExecutor) 查询的时候首先根据条件生成 CacheKey(具体细节看源码), 再根据 cacheKey 查询 localCache 有无结果,如果有缓存直接返回,无缓存在查询后放入缓存返回。这是 mybatis 的一级缓存。

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (this.closed) {throw new ExecutorException("Executor was closed.");
        } else {if (this.queryStack == 0 && ms.isFlushCacheRequired()) {this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {--this.queryStack;}

            if (this.queryStack == 0) {Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();}

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {this.clearLocalCache();
                }
            }

            return list;
        }
    }

如何规避掉 mybatis 一级缓存呢,通过源码来看有两种方式
1、通过 queryStack == 0 且 flushCacheRequired==true 则会清理缓存。queryStack 是每次用同一个 sqlSession 执行这个 query 方法的时候 +1,执行结束放入缓存后减 1。单线程的查询 == 0 的条件每次都能满足,多线程同一个 sqlSession 的话可能会有些问题哦。flushCacheRequired 的设置如下:

<select id="query" resultType="map" parameterType="map" flushCache="true"> 
</select>

2、queryStack== 0 且 configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) 会清理缓存。
localcacheScope 的设置如下:

<settings>
        <setting name="localCacheScope" value="STATEMENT"/>
    </settings>

本篇下记录到此,后续会看一下二级缓存是否也造成内存溢出的情况。

正文完
 0