优化大型MySQL查询

时间:2010-11-01 05:46:47

标签: 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行。

我可以做些什么来优化有问题的查询?我需要添加索引吗?我怎样才能让事情变得更好?

3 个答案:

答案 0 :(得分:2)

您可以尝试以下索引来调整该语句

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

这只是我的第一个想法。

您还可以添加CampaignIDType以提高效率 - 如果它们具有选择性。如果同时添加两者,您甚至可以尝试添加Email以使其成为covering index

但是,该表上的索引数量相当高(8)。其中两个是冗余的(id_email,id_campaignid),因为还有其他的以同一列(id_emailtype,idx_cidtype)开头。

请注意(原则上)一个表访问只使用一个索引。您的查询只有一个表访问权限(没有子查询,连接,UNION左右),因此它只能使用一个索引。因此,您需要一个索引,该索引尽可能支持您的where子句。

请注意,该索引中列的顺序很重要。我首先添加了具有完全匹配的那些(EmailDomainListID),然后是使用不等于运算符(Date)的那个 - 假设第一个{{ 1}}仍然相当有选择性。不等于操作之后的所有内容都只是索引中的过滤器 - 如果需要,您可以在此处添加Date列表。

广告

万一您希望了解有关数据库索引的更多信息:请查看我的free eBook on database indexing

答案 1 :(得分:2)

调优查询的范围很小,但你可以通过调整数据库模式使其速度更快 - 诀窍是确定一个尽可能具体的潜在索引。

e.g。

  

AND日期> = DATE_SUB(现在(),间隔90天)

建议“日期”的索引可能有所帮助 - 但前提是您的数据至少在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)

你可以简单地在(type,emailDomain,ListId,CampaignId和Date)上添加索引但是我怀疑CampaignId和Date具有最大的基数,因此应该出现在索引的前面 - 索引应该在输入数据集(表)中的基数与查询输出的比率。例如如果您经常使用以下命令运行查询:

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

然后你不会从索引前面的Date获得那么多的好处。类似地,看起来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操作数。

然而,在担心订单之前确定哪些字段出现在索引中更为重要。

答案 2 :(得分: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执行此操作;然而,我没有为我的测试设置。另请注意,我从大慢查询中取出函数调用并将其转换为常量。