2013-03-12 116 views
2

我的查詢生成一些關於加速,上次和平均速度的報告。 這是我的查詢:查詢優化 - 花費太長時間並停止服務器

Select 
    r1 . *, r2.name, r2.notes, r2.serial 
From 
    (SELECT 
     k.idgps_unit, 
      MIN(k.dt) AS DT_Start, 
      MIN(CASE 
       WHEN k.RowNumber = 1 THEN k.Lat 
      END) AS Latitude_Start, 
      MIN(CASE 
       WHEN k.RowNumber = 1 THEN k.Long 
      END) AS Longitude_Start, 
      MIN(CASE 
       WHEN k.RowNumber = 1 THEN k.Speed_kmh 
      END) AS Speed_Start, 
      MAX(k.dt) AS dt_end, 
      MIN(CASE 
       WHEN k.RowNumber = MaxRowNo THEN k.Lat 
      END) AS Latitude_End, 
      MIN(CASE 
       WHEN k.RowNumber = MaxRowNo THEN k.Long 
      END) AS Longitude_End, 
      MIN(CASE 
       WHEN k.RowNumber = MaxRowNo THEN k.Speed_kmh 
      END) AS Speed_End, 
      AVG(Speed_kmh) AS Average_Speed 
    FROM 
     (SELECT 
     gps_unit_location . *, 
      @i:=CASE 
       WHEN Speed_Kmh > 80 AND @b = 0 THEN @i + 1 
       ELSE @i 
      END AS IntervalID, 
      @r:=CASE 
       WHEN Speed_Kmh > 80 AND @b = 0 THEN 1 
       ELSE @r + 1 
      END AS RowNumber, 
      @b:=CASE 
       WHEN Speed_Kmh > 80 THEN 1 
       ELSE 0 
      END AS IntervalCheck 
    FROM 
     gps_unit_location, (SELECT @i:=0) i, (SELECT @r:=0) r, (SELECT @b:=0) b 
    ORDER BY dt , idgps_unit_location) k 
    INNER JOIN (SELECT 
     IntervalID, MAX(RowNumber) AS MaxRowNo 
    FROM 
     (SELECT 
     gps_unit_location . *, 
      @i:=CASE 
       WHEN Speed_Kmh > 80 AND @b = 0 THEN @i + 1 
       ELSE @i 
      END AS IntervalID, 
      @r:=CASE 
       WHEN Speed_Kmh > 80 AND @b = 0 THEN 1 
       ELSE @r + 1 
      END AS RowNumber, 
      @b:=CASE 
       WHEN Speed_Kmh > 80 THEN 1 
       ELSE 0 
      END AS IntervalCheck 
    FROM 
     gps_unit_location, (SELECT @i:=0) i, (SELECT @r:=0) r, (SELECT @b:=0) b 
    ORDER BY dt , idgps_unit_location) d 
    WHERE 
     IntervalCheck = 1 
    GROUP BY IntervalID) MaxInt ON MaxInt.IntervalID = k.IntervalID 
    WHERE 
     k.IntervalCheck = 1 
      and k.idgps_unit in (SELECT 
       idgps_unit 
      FROM 
       instafleet.gps_unit 
      where 
       id_customer = (select 
         idcustomer 
        from 
         user 
        where 
         iduser = 14)) 
    GROUP BY k.IntervalID , k.idgps_unit) r1 
     Inner join 
    gps_unit r2 ON r1.idgps_unit = r2.idgps_unit 

目前,它需要3分鐘783723條記錄。我認爲適當的指標可能會有所幫助;雖然經過一些試驗和錯誤,但我無法弄清楚。如果你認爲你可以提供幫助,並需要一些額外的信息 - 我會很高興你提供給你。

解釋 Explain

結果 Result

+3

這是一個很長的查詢別人來優化您的免費重寫。你可以把它分解成更小的查詢來隔離問題嗎? – 2013-03-12 17:17:31

+1

Marcus,我認爲添加合適的索引可以解決它的性能問題..不需要重做它...我可能是錯的:) – Andrew 2013-03-12 17:20:39

+0

您正在使用哪種GUI軟件? – dynamic 2013-03-20 18:33:47

回答

2

添加一個索引有助於在許多情況下,但你有一個子查詢加盟另一子查詢,在當前表沒有索引可以幫助你加快。您可以在這裏使用索引的唯一方法是創建臨時表。

正如Markus指出的那樣,您需要將您的查詢分解爲幾個較小的查詢,這些查詢將結果存儲在臨時表中。比你可以添加索引給他們,並希望加快你的查詢。將大問題分解爲幾個較小問題的另一個好處是,您可以更好地分析哪個部分是較慢的問題並修復問題。

您還使用了一次子查詢兩次,這對性能不利,因爲結果未被緩存。

這裏是你如何能做到這一點的例子:如果在嵌套查詢(S)的字節數超過了緩衝池的大小(檢查innodb_buffer_pool_size)查詢將採取

DROP TEMPORARY TABLE IF EXISTS tmp_k; 
CREATE TEMPORARY TABLE tmp_k 
    ENGINE=Memory 
SELECT 
    gps_unit_location.*, 
    @i:= IF(((Speed_Kmh > 80) AND (@b = 0)), @i + 1, @i) AS IntervalID, 
    @r:= IF(((Speed_Kmh > 80) AND (@b = 0)), 1, @r + 1) AS RowNumber, 
    @b:= IF((Speed_Kmh > 80), 1, 0) AS IntervalCheck 
FROM 
    gps_unit_location, 
    (SELECT @i:=0) i, 
    (SELECT @r:=0) r, 
    (SELECT @b:=0) b 
ORDER BY 
    dt, 
    idgps_unit_location; 

ALTER TABLE tmp_k ADD INDEX (IntervalID); 

DROP TEMPORARY TABLE IF EXISTS tmp_max; 
CREATE TEMPORARY TABLE tmp_max 
    ENGINE=Memory 
SELECT 
    IntervalID, 
    MAX(RowNumber) AS MaxRowNo 
FROM 
    temp_k 
WHERE 
    IntervalCheck = 1 
GROUP BY 
    IntervalID; 

ALTER TABLE tmp_max ADD INDEX (IntervalID); 

SELECT 
    k.idgps_unit, 
    MIN(k.dt) AS DT_Start, 
    MIN(IF(k.RowNumber = 1, k.Lat, NULL)) AS Latitude_Start, 
    MIN(IF(k.RowNumber = 1, k.Long, NULL)) AS Longitude_Start, 
    MIN(IF(k.RowNumber = 1, k.Speed_kmh, NULL) AS Speed_Start, 
    MAX(k.dt) AS DT_End, 
    MIN(IF(k.RowNumber = m.MaxRowNo, k.Lat, NULL)) AS Latitude_End 
    MIN(IF(k.RowNumber = m.MaxRowNo, k.Long, NULL)) AS Longitude_End 
    MIN(IF(k.RowNumber = m.MaxRowNo, k.Speed_kmh, NULL)) AS Speed_End, 
    AVG(Speed_kmh) AS Average_Speed, 
    gu.name, 
    gu.notes, 
    gu.serial 
FROM 
    tmp_k AS k 
    INNER JOIN tmp_max AS m 
     USING(IntervalID) 
    INNER JOIN gps_unit AS gu 
     USING(idgps_unit) 
    INNER JOIN user AS u 
    ON (gu.idcustomer = u.idcustomer) 
WHERE 
    (k.IntervalCheck = 1) 
    AND (u.iduser = 14) 
GROUP BY 
    k.IntervalID, 
    k.idgps_unit; 

DROP TEMPORARY TABLE tmp_k; 
DROP TEMPORARY TABLE tmp_max; 
0

由於I/O分頁非常長時間。

這就是說,你可以改善與下面的提示你的表現:

  • 在嵌套查詢
  • 增加緩衝池的大小,選擇儘可能少的數據成爲可能。
0

我個人的經驗表明,MySQL在處理子查詢時相當糟糕。數據庫的查詢優化器是數據庫中非常複雜和美味的一部分,商業數據庫供應商付出了很多努力,所以這是恕我直言,難怪MySQL在處理由更瘋狂的開發人員發明的瘋狂SQL語句時表現得相當差;-)。

在這裏看到:http://dev.mysql.com/doc/refman/5.6/en/subquery-restrictions.html

優化是比子查詢的連接更加成熟,所以在很多 情況下,使用一個子查詢語句可以執行更 如果有效,你把它改寫爲聯合, 。

如果從Oracle狀態的東西MySQL官方文檔,如「更成熟」,那麼你可以放心,它實際上是一個類似於廢話(沒有雙關語意,但我已經有我的問題與MySQL和大多數的用商業數據庫完美運行的大型語句,寧可殺死mysql)。

所以任務是:使用連接....