![](/img/trans.png)
[英]Why does my query take over 30 mins on MySQL 5.7 but a couple seconds on MySQL 8?
[英]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.