PreparedStatement 比使用 JDBC 的 Statement 慢

Posted

技术标签:

【中文标题】PreparedStatement 比使用 JDBC 的 Statement 慢【英文标题】:PreparedStatement slower than Statement with JDBC 【发布时间】:2018-03-01 15:08:42 【问题描述】:

我目前正在从事天气监测工作。 例如,温度记录具有日期和位置(坐标)。 所有坐标已经在数据库中,我需要添加的是时间和温度值。值和元数据位于 CSV 文件中。 基本上我正在做的是:

    通过文件名获取时间 将时间插入数据库,并保留主键 读取文件,获取数值和坐标 选择查询获取坐标的id 使用外键(时间和坐标)插入天气值

问题是

"SELECT id FROM location WHERE latitude = ... AND longitude = ..."

太慢了。我有 230k 个文件,目前处理一个文件需要超过 2 分钟...编辑:通过更改索引,现在需要 25 秒,但仍然太慢。此外,PreparedStatement 也仍然较慢,我不知道为什么。

private static void putFileIntoDB(String variableName, ArrayList<String[]> matrix, File file, PreparedStatement prepWeather, PreparedStatement prepLoc, PreparedStatement prepTime, Connection conn)
    try 

        int col = matrix.size();
        int row = matrix.get(0).length;

        String ts = getTimestamp(file);
        Time time = getTime(ts);

        // INSERT INTO takes 14ms
        prepTime.setInt(1, time.year);
        prepTime.setInt(2, time.month);
        prepTime.setInt(3, time.day);
        prepTime.setInt(4, time.hour);
        ResultSet rs = prepTime.executeQuery();
        rs.next();
        int id_time = rs.getInt(1);


        //for each column (longitude)
        for(int i = 1 ; i < col ; ++i)

            // for each row (latitude)
            for(int j = 1 ; j < row ; ++j)

                try 
                    String lon = matrix.get(i)[0];
                    String lat = matrix.get(0)[j];
                    String var = matrix.get(i)[j];
                    lat = lat.substring(1, lat.length()-1);
                    lon = lon.substring(1, lon.length()-1);
                    double latitude = Double.parseDouble(lat);
                    double longitude = Double.parseDouble(lon);
                    double value = Double.parseDouble(var);

                    // With this prepared statement, instruction needs 16ms to be executed
                    prepLoc.setDouble(1, latitude);
                    prepLoc.setDouble(2, longitude);
                    ResultSet rsLoc = prepLoc.executeQuery();
                    rsLoc.next();
                    int id_loc = rsLoc.getInt(1);

                    // Whereas this block takes 1ms
                    Statement stm = conn.createStatement();
                    ResultSet rsLoc = stm.executeQuery("SELECT id from location WHERE latitude = " + latitude + " AND longitude =" + longitude + ";" );
                    rsLoc.next();
                    int id_loc = rsLoc.getInt(1);


                    // INSERT INTO takes 1ms
                    prepWeather.setObject(1, id_time);
                    prepWeather.setObject(2, id_loc);
                    prepWeather.setObject(3, value);
                    prepWeather.execute();

                 catch (SQLException ex) 
                    Logger.getLogger(ECMWFHelper.class.getName()).log(Level.SEVERE, null, ex);
                
            

        
     catch (SQLException ex) 
        Logger.getLogger(ECMWFHelper.class.getName()).log(Level.SEVERE, null, ex);        
    


我已经做了什么:

在表位置的纬度和经度列上设置两个B树索引 删除外键约束

参数中的PreparedStatements是:

        // Prepare selection for weather_radar foreign key
        PreparedStatement prepLoc = conn.prepareStatement("SELECT id from location WHERE latitude = ? AND longitude = ?;");

        PreparedStatement prepTime = conn.prepareStatement("INSERT INTO time(dataSetID, year, month, day, hour) " +
                "VALUES(" + dataSetID +", ?, ? , ?, ?)" +
                        " RETURNING id;");            

        // PrepareStatement for weather_radar table
        PreparedStatement prepWeather = conn.prepareStatement("INSERT INTO weather_radar(dataSetID, id_1, id_2, " + variableName + ")"
                            + "VALUES(" + dataSetID + ", ?, ?, ?)");

有什么想法可以让事情进展得更快吗?

Ubuntu 16.04 LTS 64 位 15.5 焦 Intel® Core™ i7-6500U CPU @ 2.50GHz × 4 x86_64-pc-linux-gnu 上的 PostgreSQL 9.5.11,由 gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609,64 位编译 Netbeans IDE 8.2 JDK 1.8 postgresql-42.2.0.jar

【问题讨论】:

只是想提供帮助。你确定你的索引被击中了吗?它的定义是什么?由于精度问题,您确定没有遗漏(即数据库内的纬度与您从文件中读取的纬度不完全相同,因此索引无用)?缓存位置是否合理?索引例如是否合理?将 lon/lat 连接到固定精度的字符串(非唯一索引,但基数要小得多)? 其实我不知道有没有被击中,我怎么知道呢?你的定义是什么意思?它只是主键上的一个 b-tree。精度没有问题,数据库是由这些文件提供的。对于最后两个问题,我不确定你的意思。 “位置”表中有 21594 行(122 纬度和 177 长)。 显示你的表和索引定义,使用auto_explain捕获PostgreSQL日志中Java的执行计划,在语句上运行EXPLAIN (ANALYZE, BUFFERS)。有了所有这些信息,我们应该可以说更多。 @Issou 在主键上放置索引将(显然?)对在主键上没有谓词的查询的性能影响为零(例如,它将加速 WHERE ID = X 但没有对WHERE lat=X and lon=Y的影响)。 您应该在纬度经度上添加单个索引,这样更有效。 【参考方案1】:

    您遇到的关键问题是您错过了ResultSet.close()Statement.close() 类型的电话。

    当您解决该问题(添加相关的关闭调用)时,您可能会发现使用 SINGLE con.prepareStatement 调用(在两个 for 循环之前)会进一步提高性能(当然,您不需要关闭语句在循环中,但是您仍然需要在循环中关闭结果集)。

    那么你可以应用批处理 SQL

【讨论】:

【参考方案2】:

使用EXPLAIN,可以找出查询潜伏的点。

我遇到类似情况的一种情况是:

复合查询,例如参数化的相似日期范围,来自不同的表,然后将它们加入某个索引值。即使上面的日期作为索引,preparedStatement 中生成的查询仍然无法命中索引并最终对连接数据进行扫描。

【讨论】:

以上是关于PreparedStatement 比使用 JDBC 的 Statement 慢的主要内容,如果未能解决你的问题,请参考以下文章

PreparedStatement 和 Statment区别

不允许 HSQLDB 参数标记

使用PreparedStatement接口操作数据库

JDBC——PreparedStatement执行SQL的对象

使用 jdb 调试 Java servlet。如何将 jdb 与 Tomcat 连接

Statement和PreparedStatement之间的区别