后台程序 报错内容:java.sql.SQLException: Java heap space
问题
使用 MybatisPlus 的游标查询,报错 java.sql.SQLException: Java heap space
原因分析
SQLException 引发 Java heap space 溢出可能是以下原因导致的:
- 查询没有通过参数,而是直接查询出所有的记录。
- 是由于 mysql 单个字段存储的内容过大导致堆内存溢出。
- JVM 启动时,JVM 堆会自动设置 heap size 值,值太小导致;
因本次查询使用游标的方式,故优先排除 原因 1 导致的异常。
step1:通过调大 JVM 的最大堆内存和初始堆内存,并没有解决问题
step2:通过打印单条 SQL 结果,发现 单条 SQL 返回结果很大
问题定位初步定位到 原因 2
解决方法
- MybatisPlus 流式查询涉及到的方法增加 @Options(fetchSize = Integer.MIN_VALUE, resultSetType = ResultSetType.FORWARD_ONLY) 注解
@Options(fetchSize = Integer.MIN_VALUE, resultSetType = ResultSetType.FORWARD_ONLY)
void selectList(@Param(Constants.WRAPPER)Wrapper<T> queryWrapper, ResultHandler<T> resultHandler);
其中:FetchSize:MySQL 服务端单次发送至客户端的数据条数。ResultSetType:结果集读取方式
ResultSetType 有 4 种可选项,我们点进去看看,DEFAULT 不谈,主要是下面三种:
DEFAULT(-1),
FORWARD_ONLY(1003),
SCROLL_INSENSITIVE(1004),
SCROLL_SENSITIVE(1005);
- FORWARD_ONLY,顾名思义只能向前,即数据只能向前读取,是不是就类似一个流水的管道,读一条就相当于水流过去一些。也是我们需要选用的
- SCROLL_INSENSITIVE,不敏感滚动,和下面那个差不多,都是可以向后读或向前读;这意味着已读取过的数据不能丢掉,要继续保存在内存中,因为有可能会回去再次读取他们
- SCROLL_SENSITIVE,敏感滚动,和上面那个差不多
这么一比较就看得出来,当选的一定是 FORWARD_ONLY,我们亟需解决的就是大数据量对内存的影响,再用后面两个还是会放在内存中
FetchSize 这个概念在许多服务中都有提及,例如 RabbitMQ 中是消费者取过来预处理的消息数量,但在 MySQL 中完全不是一个概念。MySQL 的数据传输是基于 C/S 的阻塞机制,即 Client 设置 FetchSize = 1000,而 Server 查出来 10000 条数据,按照常理应该是 Server 智能地使用分页策略 1000 条 1000 条取;实际不是,Server 查出来多少就是多少,他会放在自己特定的内存空间内,只是会根据 FetchSize 的大小一点一点传送给 Client——利用 C/S 的通讯阻塞,发 1000 条、堵一下、发 1000 条、堵一下……。
话又说回来,怎么配置这个 FetchSize 呢?JDBC 官方给出的答案是设置为“Integer.MIN_VALUE”,具体原因不清楚,但我猜是为了和游标查询区分开,因为一会你会发现流式查询和游标查询唯一的区别就是 FetchSize 的大小。
注意:
- JDBC 查询默认是不支持 FetchSize 属性的,需要在 JDBC 连接 URL 后面加上“useCursorFetch=true”。
- 如何判断自己是否使用了流式查询或游标查询,下面是几个数据集的对应关系
| 查询方式 | 结果集类型 | 行数据类型 |
| -------- | ---------------------- | -------------- |
| 普通分页 | ResultsetRowsStatic | RowDataStatic |
| 流式查询 | ResultsetRowsStreaming | RowDataDynamic |
| 游标查询 | ResultsetRowsCursor | RowDataCursor |
License:
CC BY 4.0