首页 > 解决方案 > 应用结果集的 fetch size 会杀死程序

问题描述

这对我来说是一个非常有趣的问题,我希望你能帮助我解决它。我正在查询以从 Oracle DB 表中选择所有行。这里使用了 oracle jdbc 驱动程序。为避免连接超时,使用 rownum 以 100 行为增量执行查询。一切都会好的,但是程序在结果集的第 91 行冻结,试图执行 resultSet.next()。其中没有任何例外。我试图寻找这种行为的原因,并意识到问题在于结果集的获取大小。fetch size 的默认值为 10。这种行为看起来就像这 10 行从结果集中被拉出,当我们进入释放空间时程序被冻结。然后我们将 fetch size 设置为 0,一切正常。这是预期的行为吗?如果是这样,为什么?在下面的示例中,

private static volatile int bottomRow = -99;
private static volatile int topRow = 0;
private static final String SQL = "SELECT * from (select m.*, rownum r from keyspace.table m) where r >= ? and r < ?";

public static void select(Connection connection) {
    try (PreparedStatement preparedStatement=connection.prepareStatement(SQL)){
        while (true) {
            incrementCounters();
            preparedStatement.setInt(1, bottomRow);
            preparedStatement.setInt(2, topRow);
            ResultSet rs = preparedStatement.executeQuery();
          // rs.getFetchSize(); -> default value is 10
            if (rs.next()) {
                do {
                    rs.getString("id");
                    rs.getString("customer_name");
                    /* some logic */
                    if (rs.getRow() == 90) {
                            break;
                    }
                 } while (rs.next());
            } else break;
        }
    } catch (Exception e) {
    } 
}

private synchronized static void incrementCounters() {
    Thread.sleep(700);
    if (topRow != 0) {
        bottomRow += 90;
        topRow = bottomRow + 100;
    } else {
        bottomRow += 100;
        topRow += 100;
    }
}

jdbc驱动版本

<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>jdbc</artifactId>
    <version>11.2.0.3</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>11.2.0.3</version>
</dependency>

池属性的配置

private static DataSource ds=null;
public static Connection getConnection() throws SQLException{
    if (ds==null){
        synchronized (Source.class.getName()) {
            if (ds==null)
                try {
                    DriverManager.setLoginTimeout(1);
                    String driverClassName="oracle.jdbc.OracleDriver";
                    PoolProperties p = new PoolProperties();
                    p.setUrl(url);
                    p.setDriverClassName(driverClassName);
                    p.setUsername(username);
                    p.setPassword(password);
                    p.setJmxEnabled(false);
                    p.setTestWhileIdle(false);
                    p.setTestOnBorrow(true);
                    p.setValidationQuery("SELECT 1 from dual");
                    p.setTestOnReturn(false);
                    p.setTestOnConnect(false);
                    p.setValidationInterval(5*1000);
                    p.setTimeBetweenEvictionRunsMillis(120000);
                    p.setMaxActive(500);
                    p.setInitialSize(0);
                    p.setMinIdle(30);
                    p.setMaxIdle(100);
                    p.setRemoveAbandonedTimeout(60);
                    p.setMinEvictableIdleTimeMillis(120000);
                    p.setLogAbandoned(false);
                    p.setRemoveAbandoned(true);
                    p.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.ConnectionState; org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer; org.apache.tomcat.jdbc.pool.interceptor.StatementCache");
                    ds = new DataSource();
                    ds.setPoolProperties(p);
                } catch (Exception e) {
                    log.error("error {}",e.getMessage());
                    throw new RuntimeException(e);
                }
        }
    }
    return ds.getConnection();
}

标签: javaoracleresultsetojdbc

解决方案


我无法真正回答您的问题,因为缺少很多细节,但我必须在这里指出的一件事是代码似乎不必要地超出了顶部,这可能是它看似错误的行为的原因.

如果您要选择所有行(或为此运行带有条件的选择),我建议您使用简单select * from [table]的并将其余的排除在外。

您必须了解的是,JDBC 驱动程序处理的不仅仅是查询编译和数据传输,过于复杂的查询会阻止它优化您正在做的任何事情。

此外,请记住,使用边界运行多个选择会产生大量开销,特别是在 Oracle 上,即使有良好的索引,也并不总是有效。

例如,运行查询以获取 1000 条记录会创建一个结果集、打开一个流并查找记录、过滤它们、对它们进行排序、以 JDBC 驱动程序认为最有意义的任何批量大小传输记录。(在数据库端,只有一个指针从第一个移动到最后一个)

运行相同的东西,但使用硬编码 100 个记录大小运行 10 次会产生开销,其中需要创建 10 个结果集,数据库服务器需要查找记录 10 次,过滤它们 10 次,对它们进行排序 10 次并跳过适当的数量以到达您请求的批次,这意味着 0,然后是 100,然后是 200...(在 db 端,每个批次都必须创建一个新指针,然后在传输开始之前将其移动到适当的位置)

现在,对于 1000 条记录,这并不重要,但是始终编写弹性代码是一种很好的做法,但是如果您必须在具有 2000 万条记录的表上执行此操作(我已经这样做了,这就是我学到的)这项工作从(在我的情况下)几分钟到几周。

至于超时,除非处理您已经传输的数据需要很长时间,否则它们不是问题,但如果是这种情况,我不会寻求查询优化,而是一旦数据处理已经进入(也许引入某种形式的并行项以同时完成更多工作)。

如果,对于您的具体情况,您可以提供更多详细信息,例如表中存储的内容、记录的数量等等,我会很高兴更新我的答案。

希望有帮助。


推荐阅读