2012-11-30 64 views
0

我現在有一張包含18,310,298條記錄的表。如何優化對大表的查詢

而且一個查詢

SELECT COUNT(obj_id) AS cnt 
FROM 
`common`.`logs` 
WHERE 
`event` = '11' AND 
`obj_type` = '2' AND 
`region` = 'us' AND 
DATE(`date`) = DATE('20120213010502'); 

有了一個結構

CREATE TABLE `logs` (
    `log_id` int(11) NOT NULL AUTO_INCREMENT, 
    `event` tinyint(4) NOT NULL, 
    `obj_type` tinyint(1) NOT NULL DEFAULT '0', 
    `obj_id` int(11) unsigned NOT NULL DEFAULT '0', 
    `region` varchar(3) NOT NULL DEFAULT '', 
    `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 
    PRIMARY KEY (`log_id`), 
    KEY `event` (`event`), 
    KEY `obj_type` (`obj_type`), 
    KEY `region` (`region`), 
    KEY `for_stat` (`event`,`obj_type`,`obj_id`,`region`,`date`) 
) ENGINE=InnoDB AUTO_INCREMENT=83126347 DEFAULT CHARSET=utf8 COMMENT='Logs table' | 

和MySQL解釋展現在每天高峯使用時間的下一個

+----+-------------+-------+------+--------------------------------+----------+---------+-------------+--------+----------+--------------------------+ 
| id | select_type | table | type | possible_keys     | key  | key_len | ref   | rows | filtered | Extra     | 
+----+-------------+-------+------+--------------------------------+----------+---------+-------------+--------+----------+--------------------------+ 
| 1 | SIMPLE  | logs | ref | event,obj_type,region,for_stat | for_stat | 2  | const,const | 837216 | 100.00 | Using where; Using index | 
+----+-------------+-------+------+--------------------------------+----------+---------+-------------+--------+----------+--------------------------+ 
1 row in set, 1 warning (0.00 sec) 

運行這樣的查詢耗時5秒左右。

我該怎麼做才能讓它更快?

更新:關於我修正指數和起飛WHERE子句

+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | 
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| logs |   0 | PRIMARY |   1 | log_id  | A   | 15379109 |  NULL | NULL |  | BTREE  |   | 
| logs |   1 | event |   1 | event  | A   |   14 |  NULL | NULL |  | BTREE  |   | 
| logs |   1 | obj_type |   1 | obj_type | A   |   14 |  NULL | NULL |  | BTREE  |   | 
| logs |   1 | region |   1 | region  | A   |   14 |  NULL | NULL |  | BTREE  |   | 
| logs |   1 | for_stat |   1 | event  | A   |   157 |  NULL | NULL |  | BTREE  |   | 
| logs |   1 | for_stat |   2 | obj_type | A   |   157 |  NULL | NULL |  | BTREE  |   | 
| logs |   1 | for_stat |   3 | region  | A   |   157 |  NULL | NULL |  | BTREE  |   | 
| logs |   1 | for_stat |   4 | date  | A   |   157 |  NULL | NULL |  | BTREE  |   | 
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 


    mysql> explain extended SELECT COUNT(obj_id) as cnt 
    ->  FROM `common`.`logs` 
    ->  WHERE `event`= '11' AND 
    ->  `obj_type` = '2' AND 
    ->  `region`= 'est' AND 
    ->  date between '2012-11-25 00:00:00' and '2012-11-25 23:59:59'; 
+----+-------------+-------+-------+--------------------------------+----------+---------+------+------+----------+-------------+ 
| id | select_type | table | type | possible_keys     | key  | key_len | ref | rows | filtered | Extra  | 
+----+-------------+-------+-------+--------------------------------+----------+---------+------+------+----------+-------------+ 
| 1 | SIMPLE  | logs | range | event,obj_type,region,for_stat | for_stat | 21  | NULL | 9674 | 75.01 | Using where | 
+----+-------------+-------+-------+--------------------------------+----------+---------+------+------+----------+-------------+ 

看來,它的運行速度更快DATE函數的所有意見。感謝大家。

+2

在'WHERE'子句中使用函數是一個真正的性能殺手。避免'DATE'強制轉換。 –

回答

0

@Joni已經解釋過什麼是錯的索引。對於查詢,我假設您的示例查詢選擇2012-02-13的所有記錄,無論時間如何。您可以更改where子句使用>=<代替DATE演員:

SELECT COUNT(obj_id) AS cnt 
FROM 
`common`.`logs` 
WHERE 
`event` = 11 AND 
`obj_type` = 2 AND 
`region` = 'us' AND 
`date` >= DATE('20120213010502') AND 
`date` < DATE('20120213010502') + INTERVAL 1 DAY 
0

date列上的日期函數正在進行全表掃描。 試試這個::

SELECT COUNT(obj_id) as cnt 
       FROM 
        `common`.`logs` 
       WHERE 
        `event`  = 11 
       AND 
        `obj_type` = 2 

       AND 
        `region`  = 'us' 
       AND 
        `date` = DATE('20120213010502') 
+0

但我需要確切的日期不日期+時間,這意味着我必須在上一個DATE條件使用BETWEEN,而不是使用日期函數? – user1016265

+0

@ user1016265:是的,你可以使用between,直接使用date字段,你可以參考Salman的答案,並且請將解釋計劃粘貼到更新後的查詢 –

0

日誌記錄(插入)需要快速過,儘量少指標儘可能使用。

評估可能需要很長的時間,因爲這是管理員,不一定需要索引。

CREATE TABLE `logs` (
    `log_id` int(11) NOT NULL AUTO_INCREMENT, 
    `event` tinyint(4) NOT NULL, 
    `obj_type` tinyint(1) NOT NULL DEFAULT '0', 
    `obj_id` int(11) unsigned NOT NULL DEFAULT '0', 
    `region` varchar(3) NOT NULL DEFAULT '', 
    `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 
    PRIMARY KEY (`log_id`), 
    KEY `for_stat` (`event`,`obj_type`,`region`,`date`) 
) ENGINE=InnoDB AUTO_INCREMENT=83126347 DEFAULT CHARSET=utf8 COMMENT='Logs table' | 

約在日期搜索@SashiKant和@SalmanA已經回答了。

2

EXPLAIN輸出顯示查詢僅使用for_stat索引的前兩列。

這是因爲查詢在WHERE子句中沒有使用obj_id。如果你創建一個沒有obj_id一個新的密鑰(或修改現有的密鑰對列重新排序),更關鍵的,可以使用,你可能會看到更好的性能:

KEY `for_stat2` (`event`,`obj_type`,`region`,`date`) 

如果它還是太慢了,改變了過去條件,正如Salman和Sashi所說,你使用DATE()可能會改善事情。

+0

+1。添加這個索引,只需將'COUNT(obj_id)'改爲'COUNT(*)'。 'obj_id'不能爲空,所以結果將是相同的。 –

+0

@joni如何在日誌表中的索引數量?在每個插入更新中,所有索引都將被重建? – user1016265

+0

@ user1016265當插入新數據時,索引必須更新(而不是重新構建),所以它們會使INSERT變慢,但是如果您希望從SELECT中獲得合理的性能,則需要索引。你必須選擇哪一個對你更重要。 – Joni

0

是Mysql,你應該按排序規則放置索引列;表中較少可能的值 - 靠近左邊放置。 您也可以嘗試將列region更改爲枚舉()並嘗試使用BETWEEN子句搜索date。 Mysql沒有在索引中使用第三列,因爲它的使用需要更多努力,然後只是過濾(這在Mysql中是很常見的事情)。