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区别
JDBC——PreparedStatement执行SQL的对象