簡體   English   中英

MySQL查詢太慢了,我該如何改進呢?

[英]MySQL query is too slow, how can I improve this?

我有這個查詢,在一個約300.000行的表上需要大約14秒來提取數據。 該表將在不久的將來增加其規模......超過一百萬行。 我使用了EXISTS子句而不是IN子句,並且我給了一個改進。 但查詢太​​慢了。 你有解決方案嗎? 提前致謝。

這是查詢:

SELECT 
    flow,
    COUNT(*) tot
FROM
    (
        SELECT 
            ff.session_id,
            GROUP_CONCAT(ff.page, '#', ff.snippet_params,'$',ff.is_lead SEPARATOR '|') flow 
            FROM table_a ff
            WHERE EXISTS 
                (
                    SELECT
                        f.session_id
                    FROM table_a f
                    WHERE f.session_id = ff.session_id
                    AND f.is_lead = 1
                    GROUP BY f.user_id 
                    ORDER BY f.user_id, f.`timestamp` 
                )
            GROUP BY ff.user_id 
            ORDER BY ff.user_id, ff.`timestamp`, ff.session_id 
    )
AS flow
GROUP BY flow 
ORDER BY tot DESC LIMIT 10

這是解釋:

id  select_type         table       type    possible_keys       key         key_len  ref                              rows  Extra                                         
------  ------------------  ----------  ------  ------------------  ----------  -------  -----------------------------  ------  ----------------------------------------------
 1  PRIMARY             <derived2>  ALL     (NULL)              (NULL)      (NULL)   (NULL)                            532  Using temporary; Using filesort               
 2  DERIVED             ff          ALL     (NULL)              (NULL)      (NULL)   (NULL)                         322154  Using temporary; Using filesort               
 3  DEPENDENT SUBQUERY  f           ref     is_lead,session_id  session_id  767      ff.session_id       3  Using where; Using temporary; Using filesort  

ORDER BY中的額外表達式沒有任何意義,因為“ GROUP BY user_id ”將保證user_id的唯一值。

ORDER BY操作在GROUP BY操作之后應用。 如果我的目的是獲得每個user_id的最低session_id ,我會使用MIN聚合。 在原始查詢中, ORDER BY對返回的session_id沒有任何影響。 session_id返回的值是不確定的。

(其他數據庫會在此查詢中引發錯誤。對GROUP BY的特定於MySQL的擴展允許查詢運行,但我們可以通過在sql_mode中包含ONLY_FULL_GROUP_BY來獲得更多標准行為。)

EXISTS子查詢中的GROUP BY沒有任何意義。 如果找到row,則存在一行。 無需執行GROUP BY並聚合找到的行。

並且更仔細地查看它,似乎沒有必要在SELECT列表中返回session_id (在flow視圖查詢中,或在EXISTS子查詢中。)

如果我們刪除無關的語法並將查詢簡化為其本質,那么對於實際上重要的部分,我們將留下如下所示的查詢:

 SELECT flow.flow  AS flow
      , COUNT(*)   AS tot
   FROM (
          SELECT GROUP_CONCAT(ff.page,'#',ff.snippet_params,'$',ff.is_lead SEPARATOR '|') AS flow
            FROM table_a ff
           WHERE EXISTS
                 ( SELECT 1
                     FROM table_a f
                    WHERE f.is_lead = 1
                      AND f.session_id = ff.session_id
                 )
           GROUP BY ff.user_id
        ) flow
  GROUP BY flow.flow
  ORDER BY tot DESC
  LIMIT 10

查詢基本上說來擺脫(在不幸命名表)中的所有行table_a其中有一個session_id相匹配的至少一個排table_a具有相同價值session_id其中也有is_lead值1。

然后獲取所有找到的行,並根據user_id列中的值聚合它們。

在GROUP_CONCAT中沒有ORDER BY是很奇怪的,而且有點奇怪的是沒有DISTINCT關鍵字。

GROUP_CONCAT聚合返回行的不確定順序並且還可能包含重復值,這很奇怪。 (假設外部查詢將基於從GROUP_CONCAT聚合返回的值執行另一個聚合。)

但是,我不確定這個查詢應該回答什么問題。 而且我不知道什么是獨特的,什么不是。

我們知道EXISTS子查詢可以重寫為JOIN操作:

 SELECT flow.flow  AS flow
      , COUNT(*)   AS tot
   FROM (
          SELECT GROUP_CONCAT(ff.page,'#',ff.snippet_params,'$',ff.is_lead SEPARATOR '|') AS flow
            FROM ( SELECT d.session_id
                     FROM table_a d
                    WHERE d.is_lead = 1
                    GROUP BY d.session_id
                 ) e
            JOIN table_a ff
              ON ff.session_id = e.session_id
           GROUP BY ff.user_id
        ) flow
  GROUP BY flow.flow
  ORDER BY tot DESC
  LIMIT 10

我們可以努力使查詢運行得更快。 但在我這樣做之前,我想確保查詢返回一個與規范匹配的集合。 我需要確保查詢實際上回答了它旨在回答的問題。

我懷疑原始查詢不正確。 也就是說,我認為如果查詢返回“正確”的結果,它會意外地這樣做,而不是因為它保證了。 或者因為表中行的唯一性(基數)或者由於處理行的意外順序而存在特殊情況。

我想確保查詢保證在我花時間調整它並添加索引之前返回正確的結果。


問:為什么GROUP_CONCAT沒有ORDER BY 例如

 GROUP_CONCAT( foo ORDER BY something)

問:是否有特定原因沒有DISTINCT關鍵字?

 GROUP_CONCAT(DISTINCT foo ORDER BY something)

問:我們是否應該關注GROUP_CONCAT(靜默)返回截斷值的可能性? (基於group_concat_max_length變量的設置?)


跟進

為了在上面的答案中最后一個查詢的最佳性能,我建議添加以下索引:

 ... ON table_a (session_id, is_lead, page, snippet_params) 

或者任何類似的索引,將session_idis_lead作為前導列( is_lead順序),還包括pagesnippet_params列。 如果將ORDER BY添加到GROUP_CONCAT,我們可能需要稍微不同的索引。

對於外部查詢,導出的flow列沒有繞過“使用filesort”操作。 (除非您運行的是更新版本的MySQL,其中可能會創建索引。或者我們可以將查詢分解為兩個單獨的操作。一個查詢將內聯視圖實現為表,第二個查詢運行對此。)

在此子查詢中,您使用的是group by但沒有聚合函數。

對於基於組合或不具有f.session_id結果的EXIST的檢查是相同的..你應該刪除組和順序

       WHERE EXISTS 
            (
                SELECT
                    f.session_id
                FROM table_a f
                WHERE f.session_id = ff.session_id
                AND f.is_lead = 1
                GROUP BY f.user_id 
                ORDER BY f.user_id, f.`timestamp` 
            )

這條路

          WHERE EXISTS 
            (
                SELECT
                    f.session_id
                FROM table_a f
                WHERE f.session_id = ff.session_id
                AND f.is_lead = 1
            )

查看您的查詢我認為可以重構,例如:

SELECT flow ,  COUNT(*) tot
FROM (
            select 
            GROUP_CONCAT(ff.page, '#', ff.snippet_params,'$',ff.is_lead SEPARATOR '|') flow ,

  FROM table_a ff
  WHERE  f.is_lead = 1
            GROUP BY ff.user_id ) as new_flow

GROUP BY  flow
ORDER BY tot DESC LIMIT 10

您需要確保將f.session_id和f.is_lead編入索引。 它目前正在針對table_a的ff引用對中間結果中的每一行執行f的表掃描。

  • 擺脫計數(*),IIRC如果函數存在,MySQL就不能再緩存查詢,嘗試另一種方法。
  • 擺脫子查詢,IIRC MySQL也無法緩存子查詢。

很難給出這個查詢(或這些查詢)的優化版本。 您可能不想更改數據庫結構,因此它允許更簡單的查詢。 也許是其他值的一些緩存(redis等)......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM