//TODO 注意sql中指定了表名 test,如果自己执行,需要按需修改 @Select("select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table") @Options(fetchSize = Integer.MIN_VALUE) Cursor<Person> selectAll();
@Select("select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table") List<Person> selectList(); }
public SqlSession getSqlSession() { return sqlSessionFactory.openSession(); }
测试结果
1.1. 直接List接收100万数据
查询过程耗时:7833ms GC:21次 占用内存:885MB
1.2. 限制500MB内存,直接List接收100万数据
增加JVM参数 -Xmx500m
执行结果如下:
1 2
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:149)
内存溢出。
2.1. 使用游标Cursor,不配置其他参数
1 2 3 4 5 6 7 8 9 10 11
@Select("select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table") Cursor<Person> selectAll();
查询过程耗时:5908ms GC:21次 占用内存:428MB
使用游标的情况在测试中,占用了第1种情况一半的内存,处理速度也更快,GC次数也没增加。
2.2. 使用游标Cursor,不配置其他参数,限制200MB内存
等了1分30秒都没出结果,而且线程卡在MySQL传输数据上:
1 2 3 4
at java.io.FilterInputStream.read(FilterInputStream.java:133) at com.mysql.cj.protocol.FullReadInputStream.readFully(FullReadInputStream.java:64) at com.mysql.cj.protocol.a.SimplePacketReader.readMessageLocal(SimplePacketReader.java:137) at com.mysql.cj.protocol.a.SimplePacketReader.readMessage(SimplePacketReader.java:102)
3.1. 使用游标Cursor,配置 FORWARD_ONLY
1 2 3 4 5 6 7 8 9 10 11 12
@Select("select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table union all " + "select * from test.test_table") @Options(resultSetType = ResultSetType.FORWARD_ONLY) Cursor<Person> selectAll();
如果追查到JDBC层,会在 mysql 的 jdbc 驱动StatementImpl类中发现下面的方法:
1 2 3 4 5 6 7 8 9 10 11
/** * We only stream result sets when they are forward-only, read-only, and the * fetch size has been set to Integer.MIN_VALUE * * @return true if this result set should be streamed row at-a-time, rather * than read all at once. */ protectedbooleancreateStreamingResultSet() { return ((this.query.getResultType() == Type.FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY) && (this.query.getResultFetchSize() == Integer.MIN_VALUE)); }