mar*_*osh 48 java postgresql jdbc
处理数据库中的所有行(PostgreSQL)时遇到问题.我得到一个错误:org.postgresql.util.PSQLException: Ran out of memory retrieving query results.
我认为我需要读取所有行的小块,但它不起作用 - 它只读取100行(下面的代码).怎么做?
int i = 0;
Statement s = connection.createStatement();
s.setMaxRows(100); // bacause of: org.postgresql.util.PSQLException: Ran out of memory retrieving query results.
ResultSet rs = s.executeQuery("select * from " + tabName);
for (;;) {
while (rs.next()) {
i++;
// do something...
}
if ((s.getMoreResults() == false) && (s.getUpdateCount() == -1)) {
break;
}
}
Run Code Online (Sandbox Code Playgroud)
nos*_*nos 64
短版本是,调用stmt.setFetchSize(50);
并conn.setAutoCommit(false);
避免将整个内容读ResultSet
入内存.
以下是文档所说的内容:
根据游标获取结果
默认情况下,驱动程序立即收集查询的所有结果.这对于大型数据集来说可能不方便,因此JDBC驱动程序提供了一种将ResultSet基于数据库游标并仅获取少量行的方法.
在连接的客户端缓存少量行,当用尽时,通过重新定位光标来检索下一行行.
注意:
基于游标的ResultSet不能在所有情况下使用.有许多限制会使驱动程序无声地回退到同时获取整个ResultSet.
与服务器的连接必须使用V3协议.这是服务器版本7.4及更高版本的默认设置(仅受支持).-
Connection不能处于自动提交模式.后端在事务结束时关闭游标,因此在自动提交模式下,后端将关闭游标,然后才能从中获取任何内容.-
必须使用ResultSet类型ResultSet.TYPE_FORWARD_ONLY创建Statement.这是默认值,因此不需要重写代码以利用此功能,但这也意味着您无法向后滚动或以其他方式在ResultSet中跳转.-
给出的查询必须是单个语句,而不是与分号串在一起的多个语句.
例5.2.设置提取大小以打开和关闭游标.
将代码更改为游标模式就像将Statement的获取大小设置为适当的大小一样简单.将获取大小设置为0将导致所有行被缓存(默认行为).
// make sure autocommit is off
conn.setAutoCommit(false);
Statement st = conn.createStatement();
// Turn use of the cursor on.
st.setFetchSize(50);
ResultSet rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
System.out.print("a row was returned.");
}
rs.close();
// Turn the cursor off.
st.setFetchSize(0);
rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
System.out.print("many rows were returned.");
}
rs.close();
// Close the statement.
st.close();
Run Code Online (Sandbox Code Playgroud)
因此,问题的症结在于默认情况下,Postgres以“ autoCommit”模式启动,并且还需要/使用游标才能“分页”数据(例如:读取前10K个结果,然后读取接下来,接着是下一个),但是游标只能存在于事务中。因此默认设置是始终将所有行读入RAM,然后让程序在全部到达之后开始处理“第一个结果行,然后第二个结果行”,由于两个原因,它不在事务中(因此,游标(无法正常工作),并且尚未设置抓取大小。
因此,psql
命令行工具如何实现FETCH_COUNT
查询的批处理响应(其设置)是在短期事务(如果尚未打开事务)中“包装”其选择的查询,以便游标可以工作。您也可以使用JDBC做类似的事情:
static void readLargeQueryInChunksJdbcWay(Connection conn, String originalQuery, int fetchCount, ConsumerWithException<ResultSet, SQLException> consumer) throws SQLException {
boolean originalAutoCommit = conn.getAutoCommit();
if (originalAutoCommit) {
conn.setAutoCommit(false); // start temp transaction
}
try (Statement statement = conn.createStatement()) {
statement.setFetchSize(fetchCount);
ResultSet rs = statement.executeQuery(originalQuery);
while (rs.next()) {
consumer.accept(rs); // or just do you work here
}
} finally {
if (originalAutoCommit) {
conn.setAutoCommit(true); // reset it, also ends (commits) temp transaction
}
}
}
@FunctionalInterface
public interface ConsumerWithException<T, E extends Exception> {
void accept(T t) throws E;
}
Run Code Online (Sandbox Code Playgroud)
这带来了需要更少RAM的好处,而且即使您不需要节省RAM,我的结果也是整体上看运行更快。奇怪的。它还带来的好处是您对第一行的处理“启动更快”(因为它一次处理一页)。
这就是“原始postgres游标”方式以及完整的演示代码的实现方式,尽管在我的实验中,无论出于何种原因,上述JDBC方式似乎都稍快一些。
另一个选择是在所有位置都autoCommit
关闭模式,尽管您仍然必须始终为每个新语句手动指定fetchSize(或者您可以在URL字符串中设置默认的提取大小)。
归档时间: |
|
查看次数: |
100547 次 |
最近记录: |