繁体   English   中英

在大表上优化MySQL查询

[英]Optimizing MySQL query on large table

我在JDBC中使用mysql。

我有一个很大的示例表,其中包含630万行,我试图对它们执行有效的选择查询。 见下文: 在此处输入图片说明

我在表上创建了三个附加索引,如下所示: 在此处输入图片说明

像这样执行SELECT查询SELECT latitude, longitude FROM 3dag WHERE timestamp BETWEEN "+startTime+" AND "+endTime+" AND HourOfDay=4 AND DayOfWeek=3"的运行时间非常长,高达256356 ms,或者略高于四分钟。我在同一个查询中的解释给了我这个: 在此处输入图片说明

我的数据检索代码如下:

    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导致逐行读取,这是正确的吗?

任何帮助表示赞赏。

编辑在时间戳,DayOfWeek和HourOfDay上创建新索引后,我的查询运行速度加快了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); 这可能会导致许多不必要的网络往返。

根据您的问题, Timestamp列的基数(即其中不同值的数量)约为Uid列的基数的1/30。 也就是说,您有很多相同的时间戳。 这对于您的查询效率而言并不是一个好兆头。

话虽如此,您可能会尝试使用以下复合覆盖索引来加快处理速度。

CREATE INDEX 3dag_q ON ('Timestamp' HourOfDay, DayOfWeek, Latitude, Longitude)

为什么会有帮助? 因为可以通过所谓的紧密索引扫描从索引中满足整个查询。 MySQL查询引擎将随机访问具有与您的查询匹配的最小时间戳值的条目的索引。 然后它将按顺序读取索引,并从匹配的行中拉出纬度和经度。

您可以尝试在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列设置为表中的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)

我从我的跟踪应用程序推断。 这是我为提高效率所做的事情:

首先,可能的解决方案取决于您是否可以预测/控制时间间隔。 例如,每X分钟或每天一次存储快照。 假设您要在YESTERDAY显示所有事件。 您可以保存已过滤文件的快照。 这样可以大大加快速度,但是对于自定义时间间隔和实际实时报道而言,这不是可行的解决方案。

我的应用程序是LIVE,但通常在T + 5分钟内可以很好地工作(最大延迟/延迟为5分钟)。 仅当用户实际选择实时位置查看时,应用程序才会在实时数据库上打开完整查询。 因此,取决于您的应用程序的工作方式。

第二个因素:如何存储时间戳非常重要。 例如,避免使用VARCHAR 如果要转换UNIXTIME,这也将给您不必要的延迟时间。 由于您正在开发似乎是一个地理跟踪应用程序,因此时间戳将以unixtime(整数)表示。 有些设备的工作时间是毫秒,我建议不要使用它们。 1449878400而不是14498784000001449878400000 0 GMT)

我将所有geopoint日期时间保存在unixtime秒中,并且仅将mysql时间戳用于时间戳记服务器接收到该点的时间戳(与您提出的此查询无关)。

您可能会节省一些时间来访问索引视图,而不是运行完整的查询。 该时间在大型查询中是否有意义还需要测试。

最后,您可以通过不使用BETWEEN而不是将其转换为类似的东西来剃掉更多的麻烦(下面的伪代码)

WHERE (timecode > start_Time AND timecode < end_time)

看到我将>=<=更改为><因为您的时间戳几乎永远不会在精确的秒上,即使是精确的秒,无论是否显示1个地理点/时间事件,您都几乎不会受到影响。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM