2010-11-01 110 views
0

我試圖優化一個查詢,花費太長時間才能運行它。它似乎陷入了很多發送數據中,需要大約半個小時才能運行。優化大型MySQL查詢

 

$campaignIDs = "31,36,37,40,41,42,43,50,51,62,64,65,66,67,68,69,84,338,339,355,431,505,530,549,563,694,752,754,755,760,769,772,777,798,799,800,806,816,821,855,856,945,989,1007,1030,1032,1047,1052,1054,1066,1182,1268,1281,1298,1301,1317,1348,1447,1461,1471,1589,1602,1604,1615,1622,1650,1652,1709"; 

SELECT Email, Type, CampaignID 
FROM Refer 
WHERE (Type = 'V' OR Type = 'C') 
    AND (EmailDomain = 'yahoo.com') 
    AND (ListID = 1) 
    AND CampaignID IN ($campaignIDs) 
    AND Date >= DATE_SUB(NOW(), INTERVAL 90 DAY) 

下面介紹一下參考表如下所示:

 
+-------------+------------------+------+-----+-------------------+----------------+ 
| Field  | Type    | Null | Key | Default   | Extra   | 
+-------------+------------------+------+-----+-------------------+----------------+ 
| ID   | int(10) unsigned | NO | PRI | NULL    | auto_increment | 
| CampaignID | int(10) unsigned | NO | MUL | NULL    |    | 
| Type  | char(1)   | NO | MUL | NULL    |    | 
| Date  | timestamp  | NO |  | CURRENT_TIMESTAMP |    | 
| IP   | varchar(16)  | NO |  | NULL    |    | 
| Useragent | varchar(200)  | YES |  | NULL    |    | 
| Referrer | varchar(200)  | YES |  | NULL    |    | 
| Email  | varchar(200)  | NO | MUL | NULL    |    | 
| EmailDomain | varchar(200)  | YES | MUL | NULL    |    | 
| FolderName | varchar(200)  | NO |  | NULL    |    | 
| ListID  | int(10) unsigned | NO | MUL | 1     |    | 
+-------------+------------------+------+-----+-------------------+----------------+ 

這裏有指標:

 
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| Table | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | 
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| refer |   0 | PRIMARY  |   1 | ID   | A   | 148581841 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_email  |   1 | Email  | A   | 18572730 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_type  |   1 | Type  | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_emaildomain |   1 | EmailDomain | A   |   19 |  NULL | NULL | YES | BTREE  |   | 
| refer |   1 | id_campaignid |   1 | CampaignID | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_listid  |   1 | ListID  | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_emailtype |   1 | Email  | A   | 24763640 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | id_emailtype |   2 | Type  | A   | 37145460 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | idx_cidtype |   1 | CampaignID | A   |   19 |  NULL | NULL |  | BTREE  |   | 
| refer |   1 | idx_cidtype |   2 | Type  | A   |   19 |  NULL | NULL |  | BTREE  |   | 
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 

這裏的輸出EXPLAIN SELECT:

 
+----+-------------+-------+-------+------------------------------------------------------------+---------------+---------+------+---------+-------------+ 
| id | select_type | table | type | possible_keys            | key   | key_len | ref | rows | Extra  | 
+----+-------------+-------+-------+------------------------------------------------------------+---------------+---------+------+---------+-------------+ 
| 1 | SIMPLE  | Refer | range | id_type,id_emaildomain,id_campaignid,id_listid,idx_cidtype | id_campaignid | 4  | NULL | 3605121 | Using where | 
+----+-------------+-------+-------+------------------------------------------------------------+---------------+---------+------+---------+-------------+ 

有表中約有150M行。

有什麼我可以做的,以優化有問題的查詢?我是否需要添加索引或其他內容?我怎樣才能讓事情變得更好?

回答

2

你可以嘗試以下指標來調整這種說法

ALTER TABLE refer 
    ADD INDEX so_suggested (EmailDomain, ListID, Date); 

這只是我的第一個念頭。

您還可以添加CampaignIDType以使其更有效 - 如果它們具有選擇性。如果同時添加,則可以嘗試添加Email以使其成爲covering index

然而,該表上的索引數量相當高(八)。其中兩個是多餘的(id_email,id_campaignid),因爲還有其他的以相同的列開始(id_emailtype,idx_cidtype)。

請注意(原則上)一個表訪問只使用一個索引。你的查詢只有一個表訪問(沒有子查詢,連接,大約UNION),因此它只能使用一個索引。因此,您需要一個索引,儘可能支持您的where子句。

請注意,該索引中列的順序很重要。我已經添加了完全匹配的第一個(EmailDomain,ListID),然後是使用不等式運算符(Date)的那個 - 假設子句Date仍然非常有選擇性。不平等操作之後的所有內容只是索引中的一個過濾器 - 如果需要,您可以在此處添加IN列表。

廣告

萬一你想了解更多有關數據庫索引:看一看我的free eBook on database indexing

2

調整查詢的範圍很小,但通過調整數據庫模式可以大大提高速度 - 訣竅在於儘可能確定潛在的索引。

例如

和日期> = DATE_SUB(NOW(),INTERVAL 90 DAY)

表明,在 '日期' 的索引可以幫助 - 但只有當你的數據以及分佈在至少4年。

在實踐中,特別是當您只需要針對特定​​查詢時,複合索引是一個好主意 - 但索引的最佳選擇不僅取決於數據的大小和形狀,還取決於您運行的其他查詢你的數據庫。在查詢

展望:

WHERE (Type = 'V' OR Type = 'C') 
    AND (EmailDomain = 'yahoo.com') 
    AND (ListID = 1) 
    AND CampaignID IN ($campaignIDs) 
    AND Date >= DATE_SUB(NOW(), INTERVAL 90 DAY) 

你可以簡單地在(類型,emailDomain,ListId,CAMPAIGNID和日期)添加索引,但是我懷疑CAMPAIGNID和日期有最大的基數,因此應出現在索引的前面 - 索引應按輸入數據集(表格)中的基數與查詢的輸出的比率進行排序。例如如果您經常使用以下方式運行查詢:

AND Date >= DATE_SUB(NOW(), INTERVAL 90000 DAY) 

然後,您不會從在索引前面添加日期中獲得太多好處。同樣,Type看起來好像有一組非常有限的值,並且應該比CampaignId稍後出現在索引中(假設您只是隨時查看相對少量的CampaignIds)。

爲了得到基數的估計,考慮:

SELECT COUNT(records_of_type)/SUM(records_of_type) 
FROM (SELECT afield, COUNT(*) AS records_of_type 
    FROM atable) 

(高值是更具選擇性,通常應該出現在索引的前面)。

但請記住,您偶爾會看到跨列的函數依賴關係。

按基數對索引字段順序排序不會減少DBMS爲滿足查詢而必須訪問的索引節點的數量,但應導致所需的磁盤I/O操作數量減少。

然而,在擔心訂單之前,確定哪些字段出現在索引中更重要。

0

可以嘗試幾種不同的方法。

有一兩件事你可以嘗試:

$date = mysql_query("SELECT DATE_SUB(NOW(), INTERVAL 90 DAY) AS date"); 

SELECT * FROM (
    SELECT Email, Type, CampaignID 
    FROM Refer 
    WHERE (Type = 'V' OR Type = 'C') 
    AND (EmailDomain = 'yahoo.com') 
    AND (ListID = 1) 
) 
    WHERE Date >= $date 
    AND CampaignID IN ($campaignIDs) 

指數在此查詢(類型EmailDomain ListID),你應該會看到一個顯著的性能增益。你也可以玩索引的排序(但要確保查詢匹配)。 這樣做的目標是取得查詢的快速部分,並對較大數量的記錄運行該查詢,然後將查詢的較慢部分與較小的一部分進行比較。

您可能需要創建一個臨時表才能讓sql執行此操作;然而,我不必爲我的測試集。還要注意的是,我把這個大的慢查詢函數調出來,並把它變成一個常量。