我在JDBC中使用mysql。
我有一个大型示例表,其中包含我想要执行高效选择查询的630万行。见下文:
执行SELECT
这样的SELECT latitude, longitude FROM 3dag WHERE
timestamp BETWEEN "+startTime+" AND "+endTime+" AND HourOfDay=4 AND DayOfWeek=3"
查询的运行时间非常高,为256356毫秒,或略高于4分钟。我对同一个查询的解释给了我:
我的检索数据的代码如下:
Connection con = null;
PreparedStatement pst = null;
Statement stmt = null;
ResultSet rs = null;
String url = "jdbc:mysql://xxx.xxx.xxx.xx:3306/testdb";
String user = "bigd";
String password = "XXXXX";
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection(url, user, password);
String query = "SELECT latitude, longitude FROM 3dag WHERE timestamp BETWEEN "+startTime+" AND "+endTime+" AND HourOfDay=4 AND DayOfWeek=3";
stmt = con.prepareStatement("SELECT latitude, longitude FROM 3dag WHERE timestamp>=" + startTime + " AND timestamp<=" + endTime);
stmt = con.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);
rs = stmt.executeQuery(query);
System.out.println("Start");
while (rs.next()) {
int tempLong = (int) ((Double.parseDouble(rs.getString(2))) * 100000);
int x = (int) (maxLong * 100000) - tempLong;
int tempLat = (int) ((Double.parseDouble(rs.getString(1))) * 100000);
int y = (int) (maxLat * 100000) - tempLat;
if (!(y > matrix.length) || !(y < 0) || !(x > matrix[0].length) || !(x < 0)) {
matrix[y][x] += 1;
}
}
System.out.println("End");
JSONObject obj = convertToCRS(matrix);
return obj;
}catch (ClassNotFoundException ex){
Logger lgr = Logger.getLogger(Database.class.getName());
lgr.log(Level.SEVERE, ex.getMessage(), ex);
return null;
}
catch (SQLException ex) {
Logger lgr = Logger.getLogger(Database.class.getName());
lgr.log(Level.SEVERE, ex.getMessage(), ex);
return null;
} finally {
try {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(Database.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
return null;
}
}
删除while(rs.next())
循环中的每一行都会给我带来同样可怕的运行时间。
我的问题是如何优化此类查询?我很好奇.setFetchSize()
以及最佳值应该在这里。文档显示INTEGER.MIN_VALUE导致逐行获取,这是正确的吗?
感谢任何帮助。
修改 在timestamp,DayOfWeek和HourOfDay上创建一个新索引后,我的查询运行速度提高了1分钟,解释给了我这个:
答案 0 :(得分:1)
预先提出一些想法:
idex可能如下所示:
CREATE INDEX stackoverflow on 3dag(hourOfDay, dayOfWeek, Timestamp);
在MySQL中执行SQL - 你什么时候到达那里?
stmt.setFetchSize(Integer.MIN_VALUE);
这可能会造成许多不必要的网络往返。 答案 1 :(得分:1)
根据您的问题,Timestamp
列的基数(即,不同值的数量)大约是Uid
列基数的1/30。也就是说,你有很多很多相同的时间戳。这对于查询的效率来说并不是一个好兆头。
话虽如此,您可能会尝试使用以下compound covering index来加快速度。
CREATE INDEX 3dag_q ON ('Timestamp' HourOfDay, DayOfWeek, Latitude, Longitude)
为什么这会有帮助?因为您可以通过所谓的紧密索引扫描从索引中满足您的整个查询。 MySQL查询引擎将随机访问索引到具有与您的查询匹配的最小Timestamp值的条目。然后它将按顺序读取索引,并从匹配的行中拉出纬度和经度。
您可以尝试在MySQL服务器上进行一些总结。
SELECT COUNT(*) number_of_duplicates,
ROUND(Latitude,4) Latitude, ROUND(Longitude,4) Longitude
FROM 3dag
WHERE timestamp BETWEEN "+startTime+"
AND "+endTime+"
AND HourOfDay=4
AND DayOfWeek=3
GROUP BY ROUND(Latitude,4), ROUND(Longitude,4)
这可能会返回较小的结果集。 修改这会对您的纬度/经度值进行量化(舍入),然后通过将它们四舍五入来计算重复的项目数。它们越粗略地将它们四舍五入(也就是说,ROUND(val,N)
函数调用中的第二个数字越小)您将遇到更多重复值,并且查询将生成更少的不同行。更少的行节省时间。
最后,如果这些纬度/经度值是GPS导出并以度数记录,那么尝试处理超过大约四或五个小数位是没有意义的。商用GPS精度仅限于此。
更多建议
如果他们具有GPS精度,则将您的纬度和经度列设置为表格中的FLOAT
值。如果它们比GPS使用DOUBLE
更精确。在varchar(30)
列中存储和传输数字的效率非常低。
同样,将您的HourOfDay
和DayOfWeek
列设置为表格中的SMALLINT
或TINYINT
数据类型。对于0到31之间的值,64位整数是浪费的。有数百行,没关系。它有数百万。
最后,如果您的查询总是如此
SELECT Latitude, Longitude
FROM 3dag
WHERE timestamp BETWEEN SOME_VALUE
AND ANOTHER_VALUE
AND HourOfDay = SOME_CONSTANT_DAY
AND DayOfWeek = SOME_CONSTANT_HOUR
此复合覆盖索引应该是加速查询的理想选择。
CREATE INDEX 3dag_hdtll ON (HourOfDay, DayofWeek, `timestamp`, Latitude, Longitude)
答案 2 :(得分:0)
我正从我的跟踪应用推断。这就是我为效率所做的事情:
首先,可能的解决方案取决于您是否可以预测/控制时间间隔。例如,每隔X分钟或每天一次存储快照。我们假设你要在昨天显示所有事件。您可以保存已过滤文件的快照。这会极大地加快速度,但对于自定义时间间隔和真实的现场报道而言,这不是一个可行的解决方案。
我的应用程序是LIVE,但通常在T + 5分钟(最长5分钟延迟/延迟)下运行良好。只有当用户实际选择实时位置查看时,应用程序才会在实时数据库上打开完整查询。因此,取决于您的应用的工作方式。
第二个因素:如何存储时间戳非常重要。例如,避免使用VARCHAR
。如果您要转换UNIXTIME,那么也会给您带来不必要的延迟时间。由于您正在开发似乎是地理跟踪应用程序的东西,因此您的时间戳将是unixtime - 一个整数。有些设备使用毫秒,我建议不要使用它们。 1449878400
代替1449878400000
(格林威治标准时间12/12/2015)
我将所有的locationoint日期时间保存在unixtime秒中,并仅使用mysql时间戳来为服务器收到该点的时间戳添加时间戳(这与您建议的此查询无关)。
您可能需要一段时间才能访问索引视图而不是运行完整查询。这个时间在大型查询中是否重要需要进行测试。
最后,你可以通过不使用BETWEEN
并使用SIMILAR来解决它将被翻译成的内容(下面的伪代码),从而削减它们的性能。
WHERE (timecode > start_Time AND timecode < end_time)
看到我将>=
和<=
更改为>
和<
,因为很可能您的时间戳几乎永远不会出现在精确的秒数上,即使它是,您也是无论是否显示1个地缘/时间事件,都很少会被证实。