优化大表

时间:2015-12-13 13:23:45

标签: java mysql jdbc

我在JDBC中使用mysql。

我有一个大型示例表,其中包含我想要执行高效选择查询的630万行。见下文: enter image description here

我在表格上创建了​​三个额外的索引,见下文: enter image description here

执行SELECT这样的SELECT latitude, longitude FROM 3dag WHERE timestamp BETWEEN "+startTime+" AND "+endTime+" AND HourOfDay=4 AND DayOfWeek=3"查询的运行时间非常高,为256356毫秒,或略高于4分钟。我对同一个查询的解释给了我: enter image description here

我的检索数据的代码如下:

    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分钟,解释给了我这个:

enter image description here

3 个答案:

答案 0 :(得分:1)

预先提出一些想法:

  • 您实际上是否检查过SQL执行时间(从.executeQuery()到第一行?)或者执行+迭代超过630万行?
  • 您准备了PreparedStatement但是没有使用它?!
  • 使用PreparedStatement,传递tiemstamp,dayOfWeek,hourOfDay作为参数
  • 创建一个索引,以满足您的where条件。以一种可以消除排名最高字段的大多数项目的方式订购密钥。

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)列中存储和传输数字的效率非常低。

同样,将您的HourOfDayDayOfWeek列设置为表格中的SMALLINTTINYINT数据类型。对于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个地缘/时间事件,都很少会被证实。