2011-12-02 22 views
0

我試圖超過行90萬增加到MySQL查詢運行於大表

SELECT COUNT(DISTINCT device_uid) AS cnt, DATE_FORMAT(time_start, '%Y-%m-%d') AS period 
FROM game_session 
WHERE account_id = -2 AND DATE_FORMAT(time_start '%Y-%m-%d') BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE() 
GROUP BY period 
ORDER BY period DESC 

運行在一個非常大的表下面的查詢速度很慢,我有以下表結構:

CREATE TABLE `game_session` (
    `session_id` bigint(20) NOT NULL, 
    `account_id` bigint(20) NOT NULL, 
    `authentification_type` char(2) NOT NULL, 
    `source_ip` char(40) NOT NULL, 
    `device` char(50) DEFAULT NULL COMMENT 'Added 0.9', 
    `device_uid` char(50) NOT NULL, 
    `os` char(50) DEFAULT NULL COMMENT 'Added 0.9', 
    `carrier` char(50) DEFAULT NULL COMMENT 'Added 0.9', 
    `protocol_version` char(20) DEFAULT NULL COMMENT 'Added 0.9', 
    `lang_key` char(2) NOT NULL DEFAULT 'en', 
    `instance_id` char(100) NOT NULL, 
    `time_start` datetime NOT NULL, 
    `time_end` datetime DEFAULT NULL, 
    PRIMARY KEY (`session_id`), 
    KEY `game_account_session_fk` (`account_id`), 
    KEY `lang_key_fk` (`lang_key`), 
    KEY `lookup_active_session_idx` (`account_id`,`time_start`), 
    KEY `lookup_finished_session_idx` (`account_id`,`time_end`), 
    KEY `start_time_idx` (`time_start`), 
    KEY `lookup_guest_session_idx` (`device_uid`,`time_start`) 
) ENGINE=MyISAM DEFAULT CHARSET=latin1 

我該如何優化?

感謝你的答案

+0

如何慢是現在? – omarello

+1

你在做什麼'DATE_FORMAT'? –

+1

這裏有很多猜測。如果您可以發佈一些['EXPLAIN'](http://dev.mysql.com/doc/refman/5.0/en/explain.html)結果,您可能會得到更準確的答案。 –

回答

2

嘛,90mlns是很多,但我懷疑它不使用start_time_idx因爲操作,這可避免(你可以操縱你與它比較值的,如果mysql足夠聰明,每個查詢也只能執行一次),你檢查過EXPLAIN嗎?

+0

你很好,它不使用start_time_idx,它使用lookup_finished_session_idx,我不知道爲什麼 – user1078191

+0

我剛剛告訴你爲什麼:)嘗試避免格式化,請參閱我對Albin的回答如何的評論。 –

1

您可能希望按time_start進行分組和排序,而不是在運行查詢時創建的period值。按period排序需要在完成任何排序之前生成所有這些值。

+0

到了這個時候,我認爲通過'where'條件過濾後留下的記錄更少了。但是,當然對數據和結果(以及中間計數)有更多的意見會有幫助。 –

0

我會改變

BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE() 

> (CURDATE() - INTERVAL 90 DAY) 

您沒有記錄從將來,你呢?

3

DATE_FORMAT(time_start '%Y-%m-%d')聽起來很貴。
列上的每個計算都會減少索引的使用。您可能會針對每個值運行完整索引掃描+ DATE_FORMAT的計算,而不是索引查找/範圍掃描。

嘗試將計算值存儲在列中(或者如果mysql支持它,則創建計算索引)。或者甚至更好地重寫您的條件以直接與列中存儲的值進行比較。

+1

沒有必要將計算值存儲在列中,因此更容易操作與之比較的值。 –

+0

@ MichaelKrelin-hacker如果可能的話,這當然是最好的,但我認爲'DATE_FORMAT'用於截斷列中的時間部分。你如何通過操縱它所比較的​​值來做到這一點? –

+0

截斷下限並將上限提升到第二天? –

1

嘗試換掉其中具有下述條款: WHERE account_id = -2 AND time_start BETWEEN CURDATE() - INTERVAL 90 DAY AND CURDATE()

MySQL將仍然趕上之間,唯一的日期,你需要擔心的是從今天的,這可能會由於截斷技術上比午夜更大。

您可以修復,通過與CURDATE() + INTERVAL 1 DAY

+0

正確,但不要使用'BETWEEN'。使用'> ='和'

0

更改查詢遞增第二CURDATE()到:

SELECT COUNT(DISTINCT device_uid) AS cnt 
    , DATE_FORMAT(time_start, '%Y-%m-%d') AS period 
FROM game_session 
WHERE account_id = -2 
    AND time_start >= CURDATE() - INTERVAL 90 DAY 
    AND time_start < CURDATE() + INTERVAL 1 DAY 
GROUP BY DATE(time_start) DESC 

所以(account_id, time_start)索引可以用於查詢的WHERE一部分。


如果它仍然緩慢 - 在DATE(time_start)不看性能非常好 - 添加一個date_start柱和存儲time_start的日期部分。

然後加上(account_id, date_start, device_uid)的指數,這將進一步提高性能,因爲所有必要的信息 - 爲GROUP BY date_startCOUNT(DISTINCT device_uid)部分 - 將在指數:

SELECT COUNT(DISTINCT device_uid) AS cnt 
    , date_start     AS period 
FROM game_session 
WHERE account_id = -2 
    AND date_start BETWEEN CURDATE() - INTERVAL 90 DAY 
        AND CURDATE() 
GROUP BY date_start DESC 
+0

謝謝你我會嘗試 – user1078191