应用结果集的获取大小会杀死程序

时间:2019-08-25 21:31:53

标签: java oracle resultset ojdbc

这对我来说是一个非常有趣的问题,希望您能帮助我解决。我正在查询从Oracle DB表中选择所有行。这里使用了oracle jdbc驱动程序。为避免连接超时,将使用rownum以100行为增量执行查询。一切都会好起来的,但是程序将冻结在结果集的第91行,尝试执行resultSet.next()。其中没有任何例外。我试图寻找这种行为的原因,并意识到问题在于结果集的获取大小。提取大小的默认值为10。此行为看起来像是从结果集中拉出这10行,并且当我们进入释放空间时冻结了程序。然后,将抓取大小设置为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();
}

2 个答案:

答案 0 :(得分:2)

我无法真正回答您的问题,因为缺少许多细节,但是我必须在这里指出的一件事是,代码似乎不必要地位于顶部,这可能是因为貌似是错误的行为。

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

您需要了解的是,JDBC驱动程序不仅可以处理查询编译和数据传输,而且处理查询的复杂性也使它无法优化您的工作。

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

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

以相同的方式运行10次,但是硬编码100条记录大小会产生开销,这需要创建10个结果集,而db服务器需要查找记录10次,将其过滤10次,对它们进行10次排序,并跳过适当的数量转到您所请求的批处理,即0,然后是100,然后是200 ...(在数据库方面,每个批处理都必须创建一个新的指针,然后将其移动到适当的位置,然后才能开始传输)

现在,对于1000条记录来说,这并不重要,但是始终编写弹性代码是一个好习惯,但是如果您必须在具有2000万条记录的表上进行操作(我已经完成了,那就是我了解到)工作从(对我来说)几分钟到几周。

对于超时,它们不是问题,除非处理您已经传输的数据花费的时间太长,但是如果是这种情况,我就不希望查询优化,而是一旦处理完就如何处理数据in(也许引入了某种形式的并行im,以便同时完成更多工作)。

如果对于您的特定情况,您可以提供更多详细信息,例如表中存储的内容,有多少记录等等,那么我很乐意更新我的答案。

希望有帮助。

答案 1 :(得分:0)

“为避免连接超时,将使用rownum以100行为增量来执行查询”

您可以检查为什么首先发生连接超时。例如:当您尝试在sqldeveloper或toad中运行查询时,是否超时?

查询“ SELECT * from(从keyspace.table m中选择m。*,rownum r),其中r> =?并且r <?”看起来您希望实现等效的分页。我建议,如果您有兴趣获取一批要在UI上显示的数据,则希望通过内部查询对确定性的记录集进行排序。

例如:它应该是SELECT * from(从keyspace.table m ORDER BY中选择m。*,rownum r),其中r> =?并且r <?。

如果您在12c及更高版本中,请查看行限制子句的选项 https://oracle-base.com/articles/12c/row-limiting-clause-for-top-n-queries-12cr1