2017-02-09 49 views
0

我有一個查詢,其目的是爲了生成不同時期(按月,按季度,按年份等)從網站下載多少音樂作品(曲目)的統計數據。該查詢在表entityusage,entityusage_filetrack上運行。如何優化一個依賴於COUNT和GROUP BY的查詢?

要獲得下載的數量屬於一個特定的專輯我會做下面的查詢曲目:

select 
    date_format(eu.updated, '%Y-%m-%d') as p, count(eu.id) as c 
from  entityusage as eu 
inner join entityusage_file as euf 
     ON euf.entityusage_id = eu.id 
inner join track as t 
     ON t.id = euf.track_id 
where 
    t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7' 
     and entitytype = 't' 
     and action = 1 
group by date_format(eu.updated, '%Y%m%d') 

我需要設置entitytype = 't'爲entityusage可以容納其他實體的下載,以及(如果entitytype = 'a'那麼整個專輯將被下載,然後entityusage_file將保存專輯在下載時被「翻譯」的所有曲目)。

此查詢需要40 - 50秒。我一直試圖優化這個查詢一段時間,但我覺得我正在接近這個錯誤的方式。

這是必須運行以生成報告的4個類似查詢中的一個。報告最好能夠在用戶等待時完成。現在,我看着3-4分鐘。等待很長時間。

此查詢是否可以通過索引進一步優化,還是需要採取另一種方法才能完成此項工作?

CREATE TABLE `entityusage` (
    `id` char(36) NOT NULL, 
    `title` varchar(255) DEFAULT NULL, 
    `entitytype` varchar(5) NOT NULL, 
    `entityid` char(36) NOT NULL, 
    `externaluser` int(10) NOT NULL, 
    `action` tinyint(1) NOT NULL, 
    `updated` datetime NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `e` (`entityid`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1; 

CREATE TABLE `entityusage_file` (
    `id` char(36) NOT NULL, 
    `entityusage_id` char(36) NOT NULL, 
    `track_id` char(36) NOT NULL, 
    `file_id` char(36) NOT NULL, 
    `type` varchar(3) NOT NULL, 
    `quality` int(1) NOT NULL, 
    `size` int(20) NOT NULL, 
    `updated` datetime NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `file_id` (`file_id`), 
    KEY `entityusage_id` (`entityusage_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

CREATE TABLE `track` (
    `id` char(36) NOT NULL, 
    `album_id` char(36) NOT NULL, 
    `number` int(3) NOT NULL DEFAULT '0', 
    `title` varchar(255) DEFAULT NULL, 
    `updated` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', 
    PRIMARY KEY (`id`), 
    KEY `album` (`album_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC; 

上查詢的EXPLAIN給我下面的:

+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys | key   | key_len | ref       | rows | Extra          | 
+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+ 
| 1 | SIMPLE  | eu | ALL | NULL   | NULL   | NULL | NULL       | 7832817 | Using where; Using temporary; Using filesort | 
| 1 | SIMPLE  | euf | ref | entityusage_id | entityusage_id | 108  | func       |  1 | Using index condition      | 
| 1 | SIMPLE  | t  | eq_ref | PRIMARY,album | PRIMARY  | 108  | trackerdatabase.euf.track_id |  1 | Using where         | 
+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+ 
+0

爲什麼'eu.updated'並不直接,但'date_format'組合後? – i486

+0

運行在歐盟每一行更新的日期格式會降低速度。可能想看看你如何存儲數據並在插入時做這些工作。 –

+0

可能是一個錯字。但是'date_format(eu.updated,'%Y-%m-%d')!= date_format(eu.updated,'%Y%m%d')' –

回答

1

由於GROUP BY操作位於涉及函數的表達式上,因此MySQL無法使用索引來優化該操作。這將需要一個「使用文件」操作。

鑑於當前的表格定義,我相信戈登建議的索引是最好的賭注。但即使使用這些索引,「高郵」也是eu表,對所有這些行進行分塊和整理。

爲了獲得更合理的性能,您可能需要引入「預計算結果」表。這將是產生一切的計數昂貴的...但我們可以提前付出的時間代價......

CREATE TABLE usage_track_by_day 
(updated_dt DATE NOT NULL 
, PRIMARY KEY (track_id, updated_dt) 
) 
AS 
SELECT eu.track_id 
    , DATE(eu.updated) AS updated_dt 
    , SUM(IF(eu.action = 1,1,0) AS cnt 
    FROM entityusage eu 
WHERE eu.track_id IS NOT NULL 
    AND eu.updated IS NOT NULL 
GROUP 
    BY eu.track_id 
    , DATE(eu.updated) 

索引ON entityusage (track_id,updated,action)可能受益的性能。

然後,我們可以針對新的「預計算結果」表編寫一個查詢,在合理的性能下更好地拍攝。

「預計算結果」表會過時,並需要定期刷新。

這不一定是問題的最佳解決方案,但它是我們可以在數據倉庫/數據智能應用程序中使用的技術。這讓我們通過大量的細節行來獲得計數一次,然後保存這些計數以便快速訪問。

+0

我將此標記爲正確答案,因爲它讓我意識到 - 雖然可以優化原始查詢 - 但我需要採取不同的措施,以便在體面的時間範圍內從數據庫中獲取結果。我沿着你的方法行進,並在每個約50秒的時間內從4個查詢中減少到2.5秒內的單個查詢。我還應用了其他答案中的一些提示(更好的索引,ID上的ASCII歸類)。感謝您的替代方法! – sbrattla

2

這是您的查詢:

select date_format(eu.updated, '%Y-%m-%d') as p, count(eu.id) as c 
from entityusage eu join 
    entityusage_file euf 
    on euf.entityusage_id = eu.id join 
    track t 
    on t.id = euf.track_id 
where t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7' and 
     eu.entitytype = 't' and 
     eu.action = 1 
group by date_format(eu.updated, '%Y%m%d'); 

我將在track(album_id, id)entityusage_file(track_id, entityusage_id),並且entityusage(id, entitytype, action)建議指標。

+0

對索引字段的順序有什麼影響?在跟蹤的情況下,這兩個都是KEY,但是'id'是PK,我通常首先將其放入。 –

+3

訂單確實重要。如果你製作了一個像((col1,col2,col3))這樣的索引,那麼只有當你的查詢使用第一列,前兩列或者全部三列時,纔會使用該索引,但如果只使用後兩列索引則不會使用該索引。合理? – CptMisery

+0

@CptMisery是的,但是在這種情況下,兩者都用在'ON'中,而'WHERE'中的'Key'只用在這裏。 –

1

你可以試試這個。我無法真正測試它沒有你的一些樣本數據。 在這種情況下,查詢首先在表軌中查找,然後連接其他表。

SELECT 
    date_format(eu.updated, '%Y-%m-%d') AS p 
    , count(eu.id) AS c 
FROM track AS t 
INNER JOIN entityusage_file AS euf ON t.id = euf.track_id 
INNER JOIN entityusage AS eu ON euf.entityusage_id = eu.id 
WHERE 
    t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7' 
     AND entitytype = 't' 
     AND ACTION = 1 
GROUP BY date_format(eu.updated, '%Y%m%d'); 
+0

不。用SQL你主要告訴DBMS *做什麼*,而不是*如何做。首先放置'track',並不意味着DBMS將開始閱讀這張表。 –

+0

此查詢在運行數據庫的SSD的「裸機」服務器上以44.761秒執行,因爲它是唯一的任務。 – sbrattla

+0

@sbrattla - 你可以請發佈**的解釋** –

2

假設entityusage_file多半是多方面的:許多映射表,見this用於改善它的提示。請注意,它要求擺脫id並製作一對2列索引,其中之一是PRIMARY KEY(track_id, entityusage_id)。由於您的表格有一些額外的列,該鏈接並不包含所有內容。

UUID可以從108字節收縮到36,然後到16,通過去BINARY(16)並使用壓縮功能。許多存在(包括版本8.0中的內建對); here's我的。

解釋一件事......查詢執行應該已經開始於track(假設'0054a47e-b594-407b-86df-3be078b4e7b7'是非常有選擇性的)。掛斷是沒有索引從那裏到下一個表。戈登的建議指標包括這樣的。

date_format(eu.updated, '%Y-%m-%d')date_format(eu.updated, '%Y%m%d')可以簡化爲DATE(eu.updated)。 (無顯著的性能變化。)

(其他的答案和評論涉及的一些問題。我在這裏就不贅述)

+0

非常感謝您分享您對ASCII的洞察力。我採用的方法與原來的稍有不同,但我在ID上應用了ASCII排序規則。謝謝! – sbrattla

+0

好的建議。 – spencer7593