【发布时间】:2026-01-19 16:25:01
【问题描述】:
我目前正在从事天气监测工作。 例如,温度记录具有日期和位置(坐标)。 所有坐标已经在数据库中,我需要添加的是时间和温度值。值和元数据位于 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的影响)。 -
您应该在纬度和经度上添加单个索引,这样更有效。
标签: postgresql select jdbc prepared-statement benchmarking