使用内部选择慢速 MySQL 连接
Posted
技术标签:
【中文标题】使用内部选择慢速 MySQL 连接【英文标题】:Slow MySQL joins with inner select 【发布时间】:2011-08-12 08:58:22 【问题描述】:以下查询有效,但随着 sendlog 表的大小随时间增加而变慢。目标是从 newsletter_subscribers 表中选择一个列表,其中没有在 newsletter_sendlog 表中具有给定时事通讯 ID 的电子邮件条目。目前,在我的 mysql 服务器上大约需要 2.2 秒,而 sendlog 中只有几千个条目。
SELECT `newsletter_subscribers`.*
FROM `newsletter_subscribers`
INNER JOIN `newsletter_to_subscriber`
ON newsletter_to_subscriber.subscriber_id = newsletter_subscribers.id
LEFT JOIN (
SELECT `newsletter_sendlog`.`subscriber_email`
FROM `newsletter_sendlog`
WHERE (newsletter_id='7')
) AS `sendlog`
ON newsletter_subscribers.email = sendlog.subscriber_email
WHERE (sendlog.subscriber_email IS NULL)
AND (newsletter_to_subscriber.newsletter_id = '7')
EXPLAIN(query) 输出以下内容:
我对 EXPLAIN 的输出不太熟悉,但如果我阅读正确,它会表明它没有使用我在 newsletter_sendlog.subscriber_email 上定义的索引。我试过在那个表上使用 USE INDEX(email) ,但它似乎没有生效。
关于如何优化它的任何建议?或者可能建议另一个相同的查询?
newsletter_sendlog 的创建表:
CREATE TABLE `newsletter_sendlog` (
`id` int(11) unsigned NOT NULL auto_increment,
`subscriber_email` varchar(100) NOT NULL default '',
`newsletter_id` int(11) default NULL,
`sendstatus` int(11) default NULL,
`senddate` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `newsletter_id` (`newsletter_id`),
KEY `email` (`subscriber_email`)
) ENGINE=MyISAM AUTO_INCREMENT=2933 DEFAULT CHARSET=latin1;
为 newsletter_subscribers 创建表:
CREATE TABLE `newsletter_subscribers` (
`id` int(11) unsigned NOT NULL auto_increment,
`email` varchar(100) NOT NULL default '',
`name` tinytext,
PRIMARY KEY (`id`),
KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=2964 DEFAULT CHARSET=utf8;
为 newsletter_to_subscriber 创建表:
CREATE TABLE `newsletter_to_subscriber` (
`id` int(11) unsigned NOT NULL auto_increment,
`newsletter_id` int(11) NOT NULL,
`subscriber_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `newsletter_subscriber` (`newsletter_id`,`subscriber_id`)
) ENGINE=MyISAM AUTO_INCREMENT=2964 DEFAULT CHARSET=latin1;
更新:
在订阅者 ID 上添加索引后,为 newsletter_to_subscriber 创建表现在如下所示:
CREATE TABLE `newsletter_to_subscriber` (
`id` int(11) unsigned NOT NULL auto_increment,
`newsletter_id` int(11) NOT NULL,
`subscriber_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `newsletter_subscriber` (`newsletter_id`,`subscriber_id`),
KEY `subscriber` (`subscriber_id`)
) ENGINE=MyISAM AUTO_INCREMENT=2964 DEFAULT CHARSET=latin1;
@nobody 建议的查询的解释:
【问题讨论】:
【参考方案1】:最好选择特定字段,而不是星号 (*),并避免使用反引号 (`)。试试看下面的(重写的)查询是否效果更好:
SELECT
newsletter_subscribers.id,
newsletter_subscribers.email,
newsletter_subscribers.name
FROM
newsletter_subscribers
LEFT JOIN
newsletter_to_subscriber
ON
newsletter_to_subscriber.subscriber_id = newsletter_subscribers.id
LEFT JOIN
newsletter_sendlog
ON
newsletter_subscribers.email = newsletter_sendlog.subscriber_email
WHERE
newsletter_to_subscriber.newsletter_id = 7
AND
newsletter_sendlog.newsletter_id = 7
AND
newsletter_sendlog.subscriber_email IS NULL
【讨论】:
虽然非常快(大约 5 毫秒),但这个查询给出了一个空的结果集。我猜那是因为 newsletter_sendlog.newsletter_id=7 在 newsletter_sendlog.email 为 NULL 的地方不匹配?【参考方案2】:SELECT `newsletter_subscribers`.* FROM `newsletter_subscribers`
INNER JOIN `newsletter_to_subscriber`
ON newsletter_to_subscriber.subscriber_id = newsletter_subscribers.id
LEFT JOIN (
SELECT `newsletter_sendlog`.`subscriber_email` FROM `newsletter_sendlog`
WHERE (newsletter_id='7')) AS `sendlog`
ON newsletter_subscribers.email=sendlog.subscriber_email
WHERE (sendlog.subscriber_email IS NULL)
AND (newsletter_to_subscriber.newsletter_id = '7')
你可以尝试在单列上实现索引键newsletter_to_subscriber.subscriber_id
看看有没有用?
尝试使用如下表结构:
CREATE TABLE `newsletter_to_subscriber` (
`id` int(11) unsigned NOT NULL auto_increment,
`newsletter_id` int(11) NOT NULL,
`subscriber_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `newsletter_subscriber` (`newsletter_id`,`subscriber_id`)
KEY `subscriber_id_key` (`subscriber_id`)
KEY `newsletter_id_key` (`newsletter_id`)
) ENGINE=MyISAM AUTO_INCREMENT=2964 DEFAULT CHARSET=latin1;
【讨论】:
感谢您的回答,但这并没有帮助,尽管它也没有受到伤害。查询的持续时间是相同的【参考方案3】:不完全确定,但我认为索引被忽略,因为您正在寻找 NULL 值。
虽然有一种不同的、希望更有效的方式来运行此查询:
select *
from newsletter_subscribers
where email not in
(select subscriber_email
from newsletter_sendlog
where newsletter_id='7')
【讨论】:
这个查询,虽然它提供了正确的结果,但每次运行它都需要大约 20 秒才能完成。【参考方案4】:首先你不需要那个子查询:
SELECT `newsletter_subscribers`.*
FROM `newsletter_subscribers`
INNER JOIN `newsletter_to_subscriber`
ON( newsletter_to_subscriber.subscriber_id = newsletter_subscribers.id )
LEFT JOIN `newsletter_sendlog`
ON( newsletter_subscribers.email = newsletter_sendlog.subscriber_email AND
newsletter_sendlog.newsletter_id = '7' )
WHERE newsletter_sendlog.subscriber_email IS NULL
上面的查询将完成这项工作。
其次,在newsletter_to_subscriber
中,您在newsletter_id
和subscriber_id
上有一个多部分索引,您的查询不能使用它,因为它将搜索subscriber_id
,并且在索引中排在第二位,您需要在subscriber_id
上有一个单独的索引:
INDEX( subscriber_id )
【讨论】:
感谢您的回答。不幸的是,它没有帮助。该查询虽然提供了正确的结果,但与我的原始查询大约需要 2 秒相比,它需要大约 5 秒才能完成。添加索引并没有明显改变持续时间 @Jens 这很奇怪,因为第一个连接应该使用您在newsletter_to_subscriber.subscriber_id
上的索引,而第二个连接应该使用newsletter_sendlog.newsletter_id
或newsletter_sendlog.subscriber_email
上的那个匹配行较少的那个,所以查询应该不时间,在添加我提到的索引后对这个查询做一个解释,这可能有助于我们找出问题。
我为您建议的更改添加了新的创建表,并将您的查询的解释输出添加到我的原始帖子中。以上是关于使用内部选择慢速 MySQL 连接的主要内容,如果未能解决你的问题,请参考以下文章
SQL Server 2012 链接服务器到 MySQL 慢速选择查询