慢速MySQL加入内部选择

时间:2011-08-12 08:58:23

标签: mysql performance join query-optimization

以下查询有效,但随着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(查询)输出以下内容:

Output of EXPAIN

我对EXPLAIN的输出不太熟悉,但是如果我正确读取它会表明它没有使用我在newsletter_sendlog.subscriber_email上定义的索引。我尝试在该表上使用USE INDEX(电子邮件),但它似乎没有生效。

有关如何优化此功能的任何建议?或者可能建议另一个查询做同样的事情?


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;

更新

在subscriber_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:

建议的查询说明

Explain for nobody's query

4 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

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;

答案 2 :(得分:0)

不完全确定,但我认为索引被忽略,因为你正在寻找NULL值。

虽然有一种不同的,希望更有效的方式来运行此查询:

select *
from newsletter_subscribers
where email not in 
(select subscriber_email
from newsletter_sendlog
where newsletter_id='7')

答案 3 :(得分:0)

首先,您不需要该子查询:

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_idsubscriber_id上有一个多部分索引,您的查询无法使用该索引,因为它将搜索subscriber_id和在索引中排在第二位,你需要在subscriber_id上有一个单独的索引:

INDEX( subscriber_id )