Java:如何高效地从数据库读取数据?

4

我想在Java中从SQL数据库中读取一个列,并希望结果以数组形式返回。以下是函数:

public static double[] open(Connection conn,String symbol,int startdate,int enddate)                 throws SQLException {
    int id = database.get_stockid(conn, symbol);
    Statement stat = conn.createStatement();
    ResultSet rs = stat.executeQuery("select price_open from stock_data where stock_id="+id+" and date>="+startdate+" and date<="+enddate+";");
    ArrayList<Double> data = new ArrayList<Double>();
    while(rs.next()) {
        data.add(rs.getDouble("open"));
    }
    double[] data1 = new double[data.size()];
    for(int a = 0; a < data1.length; ++a) {
        data1[a]=data.get(a);
    }
    return data1;
}

这很慢。在我的sqlite数据库中需要1.5秒钟。这是读取一个列的标准方法吗?还是我做错了什么?这是我的应用程序瓶颈,所以我需要尽可能快地解决它。
编辑: 谢谢。我刚刚发现ArrayList并不是问题的原因。瓶颈一定在sql部分: 如果我只加载10天的数据,它花费的时间与加载10年的数据一样长。所以我必须改进我的sql,但怎么做呢?
以下是改进后的代码:
public static double[] open(Connection conn,String symbol,int startdate,int enddate) throws SQLException {
    int id = database.get_stockid(conn, symbol);

    PreparedStatement stat = conn.prepareStatement("select price_open from stock_data where (stock_id="+id +") and (date between "+startdate+" and "+enddate+");");
    ResultSet rs = stat.executeQuery();
    ArrayList<Double> data = new ArrayList<Double>();
    while(rs.next()) {
        data.add(rs.getDouble(1));
    }
    double[] data1 = new double[data.size()];
    for(int a = 0; a < data1.length; ++a) {
        data1[a]=data.get(a);
    }
    return data1;
}

3
你的查询选择了price_open,但结果集使用了getDouble("open")。这样做是否能按预期工作?另外,你一次获取了多少行数据? - FThompson
1
你的数据库是否正确地建立了索引? - Supericy
1
完全同意@ntalbs的观点。你是否在Java之外运行过这个查询,以便了解它单独执行时的性能?首先调整查询;确保你有正确的索引来匹配条件。然后专注于Java代码,可能将Statement切换为PreparedStatement。 - cmbaxter
1
只是一个部分的答案:避免第二个循环将ArrayList转换为数组。要么更改open的返回类型为List<Double>,要么使用ArrayListtoArray方法,或者(最好的方法)在第一个循环内或甚至在数据库中进行计算。这将避免大部分数据传输和额外的Double包装开销。 - A.H.
你的改进代码错误地使用了 PreparedStatement,并且没有实现提供给你的所有建议。 - Adam Siemion
显示剩余4条评论
3个回答

5
  1. Replace

    double[] data1 = new double[data.size()];
    for(int a = 0; a < data1.length; ++a) {
        data1[a]=data.get(a);
    }
    

    with

    double[] data1 = data.toArray(new double[data.size()]);
    
  2. Check what is the query running time (by profiling of this application or investing the logs on the database side), check if it can be reduced by e.g. introducing indexes on the columns used in the where clause i.d. stock_id and date.

  3. If you are able to estimate the amount of records your query will return or you know that it will be at least N records then instead of:

    ArrayList<Double> data = new ArrayList<Double>();
    

    invoke:

    ArrayList<Double> data = new ArrayList<Double>(AMOUNT_OF_RECORDS);
    

    this will allow to prevent expanding of the ArrayList (creating a new array of greater size and copying of the elements from the smaller array to the new bigger array).

    BTW. For the ArrayList class the default initial capacity is 10.

  4. Are the results returned from your query unique? Maybe most of the values returned from the query are duplicated? If yes, then append the DISTINCT keyword to your query:

    select distinct price_open from stock_data ...
    

    this will allow to save the time on the communication with the database and also less results returned, less results have to be processed.

  5. Use PreparedStatement instead of Statement to:

    • protect from the SQL injection
    • but also because of performance, as using PreparedStatement allows the database to reuse the already parsed query

更新 #1

  1. 请记得总是释放所有资源,例如ResultSetPreparedStatement
    • 在 Java 1.7+ 中,您可以使用新的try-with-resources语句 (Java®语言规范)。
    • 在旧的 Java 版本中,您必须将close方法的调用放在 finally 块中,并对每个调用进行单独的异常处理,以防止第一个close引发的异常阻止第二个close被调用的情况。

有助于防止SQL注入,是的。回答这个问题吗?并不太。 - Makoto
@Makoto,我太急了,又加了两个点,但是PreparedStatement不仅应该用于防止SQL注入攻击。 - Adam Siemion
使用 java.util.ListArrayList 更高效,+1 个好答案。 - Azad

2

您的查询是:

select price_open
from stock_data
where stock_id="+id+" and date>="+startdate+" and date<="+enddate+"

为了优化此操作,请在 stock_data(stock_id, date) 上创建一个索引。索引查找将用于获取数据。
如果您的数据非常大,则可以在 stock_data(stock_id, date, price_open) 上创建索引。查询仅涉及这三列,因此索引可以满足查询而无需加载原始数据页面。

1
你可以使用原始数组来提高性能,而不是使用ArrayList,但这需要您知道结果集的大小。
通过索引而不是名称引用列 - 这也可以提供轻微的改进。
 datars.getDouble(1);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接