我正在尝试优化一个太长时间无法运行的查询。它似乎停留在大量发送数据中,大约需要半小时才能运行。
$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行。
我可以做些什么来优化有问题的查询?我需要添加索引吗?我怎样才能让事情变得更好?
答案 0 :(得分:2)
您可以尝试以下索引来调整该语句
ALTER TABLE refer
ADD INDEX so_suggested (EmailDomain, ListID, Date);
这只是我的第一个想法。
您还可以添加CampaignID
和Type
以提高效率 - 如果它们具有选择性。如果同时添加两者,您甚至可以尝试添加Email
以使其成为covering index。
但是,该表上的索引数量相当高(8)。其中两个是冗余的(id_email,id_campaignid),因为还有其他的以同一列(id_emailtype,idx_cidtype)开头。
请注意(原则上)一个表访问只使用一个索引。您的查询只有一个表访问权限(没有子查询,连接,UNION
左右),因此它只能使用一个索引。因此,您需要一个索引,该索引尽可能支持您的where
子句。
请注意,该索引中列的顺序很重要。我首先添加了具有完全匹配的那些(EmailDomain
,ListID
),然后是使用不等于运算符(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执行此操作;然而,我没有为我的测试设置。另请注意,我从大慢查询中取出函数调用并将其转换为常量。