簡體   English   中英

為什么這個查詢需要超過 5 秒才能運行?

[英]Why does this query take over 5 seconds to run?

我有一個 MySQL 表,里面有大約 2m 行。 我正在嘗試運行以下查詢,每次需要超過 5 秒才能獲得結果。 我在created_at列上有一個索引。 下面是解釋EXPLAIN

這是預期的嗎?

提前致謝。

SELECT
  DATE(created_at) AS grouped_date,
  HOUR(created_at) AS grouped_hour,
  count(*) AS requests
FROM
  `advert_requests`
WHERE
  DATE(created_at) BETWEEN '2022-09-09' AND '2022-09-12'
GROUP BY
  grouped_date,
  grouped_hour

在此處輸入圖像描述

EXPLAIN 顯示type: index ,它是一個索引掃描。 也就是說,它正在使用索引,但它遍歷索引中的每個條目,就像表掃描對表中的行所做的那樣。 這由rows: 2861816 ,它告訴您優化器對它將檢查的索引條目數量的估計(這是一個粗略的數字)。 這比只檢查與條件匹配的行要昂貴得多,這是我們從索引中尋找的好處。

那么這是為什么呢?

當您在搜索中的索引列上使用任何 function 時,如下所示:

WHERE
  DATE(created_at) BETWEEN '2022-09-09' AND '2022-09-12'

它破壞了索引減少檢查行數的好處。

MySQL 的優化器對函數的結果沒有任何智能,因此它無法推斷返回值的順序將與索引的順序相同。 因此它不能使用索引排序的事實來縮小搜索范圍。 您和我都知道DATE(created_at)created_at的順序相同是很自然的,但查詢優化器不知道這一點。 還有像MONTH(created_at)這樣的其他函數,其結果肯定不是按排序順序排列的,並且 MySQL 的優化器不會嘗試知道哪個函數的結果是可靠排序的。

要修復您的查詢,您可以嘗試以下兩種方法之一:

使用表達式索引。 這是 MySQL 8.0 中的新功能:

ALTER TABLE `advert_requests` ADD INDEX ((DATE(created_at)))

注意多余的一對括號。 這些是定義表達式索引時所必需的。 索引條目是 function 或表達式的結果,而不是列的原始值。

如果您在查詢中使用相同的表達式,優化器會識別並使用索引。

mysql> explain SELECT   DATE(created_at) AS grouped_date,   HOUR(created_at) AS grouped_hour,   count(*) AS requests FROM   `advert_requests` WHERE   DATE(created_at) BETWEEN '2022-09-09' AND '2022-09-12' GROUP BY   grouped_date,   grouped_hour\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: advert_requests
   partitions: NULL
         type: range          <-- much better than 'index'
possible_keys: functional_index
          key: functional_index
      key_len: 4
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where; Using temporary

如果使用MySQL 5.7,則不能直接使用表達式索引,但可以使用虛擬列,並在虛擬列上定義索引:

ALTER TABLE advert_requests
  ADD COLUMN created_at_date DATE AS (DATE(created_at)),
  ADD INDEX (created_at_date);

優化器識別表達式的技巧仍然有效。

如果您使用早於 5.7 的 MySQL 版本,則無論如何都應該升級。 MySQL 5.6 和更早的版本到現在已經過了生命周期,它們存在安全風險。

您可以做的第二件事是重構您的查詢,使created_at列不在 function 內。

WHERE
  created_at >= '2022-09-09' AND created_at < '2022-09-13'

將日期時間與日期值進行比較時,日期值隱含在 00:00:00.000 時間。 要包括直到 2022-09-12 23:59:59.999 的每一秒,使用< '2022-09-13'會更簡單。

對此的解釋表明它使用created_at上的現有索引。

mysql> explain SELECT   DATE(created_at) AS grouped_date,   HOUR(created_at) AS grouped_hour,   count(*) AS requests FROM   `advert_requests` WHERE   created_at >= '2022-09-09' AND created_at < '2022-09-13' GROUP BY   grouped_date,   grouped_hour\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: advert_requests
   partitions: NULL
         type: range        <-- not 'index'
possible_keys: created_at
          key: created_at
      key_len: 6
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using index condition; Using temporary

此解決方案適用於舊版本的 MySQL 以及 5.7 和 8.0。

如果我正確理解了EXPLAIN ,它就可以使用索引來實現WHERE過濾。 但這將返回 280 萬行,然后必須按日期和小時分組,這是一個緩慢的過程。

您可以通過為日期和小時創建虛擬列來改進它,並為它們建立索引。

ALTER TABLE advert_requests
ADD COLUMN created_date AS DATE(created_at), ADD column created_hour AS HOUR(created_at), ADD INDEX (created_date, created_hour);

使用explain analysis ,檢查是否是Index range scan 如果不點擊此鏈接:https://dev.mysql.com/doc/refman/8.0/en/range-optimization.html選定的日期范圍。據我所知,在這種情況下優化並非易事)

暫無
暫無

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

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