# 对大型结果集使用 StreamingResult

__*useCurosrFetch*__
__*StreamingResult*__

+ JDBC 默认提前获取查询结果并将其保存在客户端内存中
+ 两种推荐的处理方式
  + 设置 `FetchSize` 为 `Integer.MIN_VALUE` 让客户端不缓存, 并使用 `StreamingResult`
  + 设置 JDBC 参数 `useCursorFetch` 为 `true`, 并设置语句参数 `FetchSize` 为正整数

```12
jdbc:mysql://<tidb_server>:<port>/<db_name>?~!@useCursorFetch=true@!~
...
preparedStatement.setFetchSize()
```

-------------------------------------------------------------------------------------

在使用 MySQL 的 JDBC 时,如果查询结果集过大,使用一次查询,可能会出现Java.lang.OutOfMemoryError: Java heap space问题,因为DB服务器端一次将查询到的结果集全部发送到Java端保存在内存中而造成OOM。

MySQL JDBC需要一条SQL从数据库读取大量数据,而不发生JVM OOM,可以采用以下方法之一:
1、当statement设置以下属性时,采用的是流数据接收方式,每次只从服务器接收部份数据,直到所有数据处理完毕,不会发生JVM OOM。

  1.1 setResultSetType(ResultSet.TYPE_FORWARD_ONLY);
  1.2 setFetchSize(Integer.MIN_VALUE); 
    1.2.1、调用statement的enableStreamingResults方法,实际上enableStreamingResults方法内部封装的就是第1种方式。

    1.2.2、设置连接属性useCursorFetch=true (5.0版驱动开始支持),statement以TYPE_FORWARD_ONLY打开,再设置fetch size参数,表示采用服务器端游标,每次从服务器取fetch_size条数据。

故采用如下方式就可以解决OOM问题:

```
ps = (PreparedStatement) con.prepareStatement("select * from bigTable",
            ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
ps.setFetchSize(Integer.MIN_VALUE);
ps.setFetchDirection(ResultSet.FETCH_REVERSE);
```

缺省时,驱动程序一次从查询里获取所有的结果。这样可能对于大的数据集来说是不方便的, 因此 JDBC 驱动提供了一个用于设置从一个数据库游标抽取若干行的 ResultSet 的方法。在连接的客户端这边缓冲了一小部分数据行,并且在用尽之后, 则通过重定位游标检索下一个数据行块。
setFetchSize 最主要是为了减少网络交互次数设计的。访问ResultSet时,如果它每次只从服务器上取一行数据,则会产生大量的开销。setFetchSize的意 思是当调用rs.next时,ResultSet会一次性从服务器上取得多少行数据回来,这样在下次rs.next时,它可以直接从内存中获取出数据而不 需要网络交互,提高了效率。 这个设置可能会被某些JDBC驱动忽略的,而且设置过大也会造成内存的上升。

一般情况下,为提升执行效率,JDBC 会默认提前获取查询结果并将其保存在客户端内存中。但在查询返回超大结果集的场景中,客户端会希望数据库服务器减少向客户端一次返回的记录数,等客户端在有限内存处理完一部分后再去向服务器要下一批。
在 JDBC 中通常有以下两种处理方式:
（1）设置 FetchSize 为 Integer.MIN_VALUE 让客户端不缓存,客户端通过 StreamingResult 的方式从网络连接上流式读取执行结果。
（2）使用 Cursor Fetch,首先需设置 FetchSize 为正整数,且在 JDBC URL 中配置 useCursorFetch = true。
TiDB 中同时支持两种方式,但更推荐使用第一种将 FetchSize 设置为 Integer.MIN_VALUE 的方式,比第二种功能实现更简单且执行效率更高。

