2013-06-18 18 views
0

我在我的應用程序中的簡單的SQL語句:SQL語句性能差,雖然使用索引

SELECT SQL_NO_CACHE key_event_id, MAX(report_ts) AS max_ts 
    FROM `key_event_reports` 
    WHERE report_model_id = 2 
    GROUP BY key_event_id; 

key_event_reports表是中等大小(〜17M行),這是表的定義:

CREATE TABLE IF NOT EXISTS `key_event_reports` (
    `key_event_report_id` int(20) NOT NULL AUTO_INCREMENT, 
    `report_model_id` int(5) NOT NULL, 
    `key_event_id` int(5) NOT NULL, 
    `title_id` int(15) NOT NULL, 
    `report_ts` datetime NOT NULL, 
    `report_time` time NOT NULL, 
    `total` int(7) NOT NULL DEFAULT '0', 
    `pos` int(7) NOT NULL DEFAULT '0', 
    `neg` int(7) NOT NULL DEFAULT '0', 
    `smooth_total` float NOT NULL DEFAULT '0', 
    `smooth_pos` float NOT NULL DEFAULT '0', 
    `smooth_neg` float NOT NULL DEFAULT '0', 
    `buzz` float NOT NULL DEFAULT '0', 
    `sentiment` float NOT NULL DEFAULT '0', 
    PRIMARY KEY (`key_event_report_id`), 
    UNIQUE KEY `key_event_id_4` (`key_event_id`,`report_model_id`,`title_id`,`report_ts`), 
    KEY `report_model_id` (`key_event_id`,`report_time`), 
    KEY `report_model_id_2` (`report_model_id`,`key_event_id`,`report_ts`), 
    KEY `key_event_id` (`key_event_id`,`report_model_id`,`report_time`,`title_id`,`smooth_total`), 
    KEY `key_event_id_3` (`key_event_id`,`report_model_id`,`report_time`,`title_id`,`smooth_pos`), 
    KEY `key_event_id_2` (`key_event_id`,`report_model_id`,`report_time`,`title_id`,`smooth_neg`), 
    KEY `get_latest_report` (`report_model_id`,`report_ts`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=16967636 ; 

report_model_id始終爲2(數據庫中沒有其他模型,但可以很快更改),並且每10分鐘報告10個不同的key_events。

這個查詢需要很長時間沒有緩存(約20秒)。這個問題變得更糟,當上面的查詢被用作一個更大的語句子查詢:

SET @report_model_id = 2; 
SET @message_id = ?; 
SET @title_id = ? 
SET @min_score = 5; 

SET @min_message_id = ( 
    SELECT MIN(message_id) 
    FROM `messages` 
    WHERE msg_time > DATE_SUB(NOW(), INTERVAL 20 MINUTE) 
); 

SELECT 
    ke.key_event_id AS key_event_id, 
    COALESCE(kermmid.message_id, MIN(mhke.message_id)) AS max_message_id, 
    ker_max.max_ts AS last_report_ts 
FROM `key_events` ke 
LEFT JOIN (
    SELECT key_event_id, MAX(report_ts) AS max_ts 
    FROM `key_event_reports` 
    WHERE report_model_id = 2 
    GROUP BY key_event_id 
) ker_max 
    ON (ker_max.key_event_id = ke.key_event_id) 
    LEFT JOIN `key_event_reports` ker 
     ON (
      ker.key_event_id = ke.key_event_id 
      AND ker.report_model_id = @report_model_id 
      AND ker.title_id = @title_id 
      AND ker.report_ts = @actcurrent 
     ) 
    LEFT JOIN `key_event_report_max_message_ids` kermmid 
     ON (
      kermmid.key_event_id = ker.key_event_id 
      AND kermmid.report_model_id = ker.report_model_id 
      AND kermmid.report_ts = ker.report_ts 
     ) 
    LEFT JOIN `messages_has_key_events` mhke 
     ON ( 
      mhke.key_event_id = ke.key_event_id 
      AND mhke.title_id = @title_id 
      AND mhke.message_id > @min_message_id 
      AND mhke.message_id < @message_id 
      AND mhke.score > @min_score 
     ) 
    GROUP BY 
     ke.key_event_id; 

如果我使用子查詢在此,執行時間從大約50毫秒到> 20多歲的太無二。

這可能是什麼原因,我怎麼可能可以優化我的陳述或數據庫結構?

+0

查詢使用哪個索引? – Jaydee

+0

這是'EXPLAIN'顯示的內容: SIMPLE; key_event_reports; REF; report_model_id_2,get_latest_report; report_model_id_2; 4;常量; 8873835;使用索引 – Thomas

+0

我發現這種行爲很奇怪,因爲'report_model_id_2'索引應該適合這個查詢:在第一個索引層(n)中取report_model_id 2的路徑,在第二個索引層中取所有key_event_ids(m)層,併爲每個取第三層中所有report_ts(l)的最大值。這應該是一個簡單的遍歷索引樹,所以它是O(log(n)+ log(m)+ log(l)),這不需要20秒。 – Thomas

回答

3

嘗試在(report_model_id,key_event_id,report_ts)上添加索引,並將report_model_id添加到組中。這應該允許它使用group by optimization

SELECT key_event_id, MAX(report_ts) AS max_ts 
FROM `key_event_reports` 
WHERE report_model_id = 2 
GROUP BY report_model_id, key_event_id 

我還在努力,爲其餘查詢想出一個辦法...沒有內部SELECT需要一個LEFT JOIN或INNER JOIN會做什麼?

編輯:我錯過了你已經擁有索引的事實,所以你只需要將該字段添加到GROUP BY。

+0

這很好地解決了這個問題。兩個查詢現在都運行得非常順利。謝謝你節省我的一天! – Thomas

2

對於「爲什麼」,我的猜測是MySQL查詢緩存。

MySQL會在某些情況下緩存查詢結果,以加快重複查詢。如果數據發生變化則必須重新運行查詢。我不知道它是如何處理子查詢的。

+0

這似乎是正確的,當我使用SQL_NO_CACHE選項時,查詢總是需要20秒。 – Thomas

1

看來你的查詢已經在使用這個索引。

`report_model_id_2` (`report_model_id`,`key_event_id`,`report_ts`) 

它包含了所有的查詢需要的信息,因此MySQL可以通過做這個指數範圍掃描,而不是對整個表,以滿足您的查詢。好消息是你已經很好地優化了查詢。這也是一個壞消息。

是否有意義創建一個彙總表,並在MySQL數據庫中設置一個事件來更新彙總表從細節數據一次?如果這個查詢的結果稍微落後一點,那麼這對你的應用程序來說並不是災難性的。

如果您必須將此信息與您的詳細信息表完全同步,那麼您也可以調整觸發器以更新彙總表。

+0

不幸的是,這是用於實時數據,所以在這種情況下執行時間很重要。我們得到了一個實時消息的饋送,這些消息是實時分析的,分數落入不同的組,給這些組提供總分。 – Thomas