簡體   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