有没有办法进一步优化这个SELECT查询?

时间:2016-04-12 14:09:38

标签: mysql sql

我有一个MySQL表,其中包含来自postfix邮件日志的邮件。该表经常更新,有时会每秒多次更新。这是SHOW CREATE TABLE输出:

Create Table postfix_mails CREATE TABLE `postfix_mails` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `mail_id` varchar(20) COLLATE utf8_danish_ci NOT NULL,
 `host` varchar(30) COLLATE utf8_danish_ci NOT NULL,
 `queued_at` datetime NOT NULL COMMENT 'When the message was received by the MTA',
 `attempt_at` datetime NOT NULL COMMENT 'When the MTA last attempted to relay the message',
 `attempts` smallint(5) unsigned NOT NULL,
 `from` varchar(254) COLLATE utf8_danish_ci DEFAULT NULL,
 `to` varchar(254) COLLATE utf8_danish_ci NOT NULL,
 `source_relay` varchar(100) COLLATE utf8_danish_ci DEFAULT NULL,
 `target_relay` varchar(100) COLLATE utf8_danish_ci DEFAULT NULL,
 `target_relay_status` enum('sent','deferred','bounced','expired') COLLATE utf8_danish_ci NOT NULL,
 `target_relay_comment` varchar(4098) COLLATE utf8_danish_ci NOT NULL,
 `dsn` varchar(10) COLLATE utf8_danish_ci NOT NULL,
 `size` int(11) unsigned NOT NULL,
 `delay` float unsigned NOT NULL,
 `delays` varchar(50) COLLATE utf8_danish_ci NOT NULL,
 `nrcpt` smallint(5) unsigned NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `mail_signature` (`host`,`mail_id`,`to`),
 KEY `from` (`from`),
 KEY `to` (`to`),
 KEY `source_relay` (`source_relay`),
 KEY `target_relay` (`target_relay`),
 KEY `target_relay_status` (`target_relay_status`),
 KEY `mail_id` (`mail_id`),
 KEY `last_attempt_at` (`attempt_at`),
 KEY `queued_at` (`queued_at`)
) ENGINE=InnoDB AUTO_INCREMENT=111592 DEFAULT CHARSET=utf8 COLLATE=utf8_danish_ci

我想知道在特定日期通过特定主机传递了多少封邮件,因此我使用此查询:

SELECT COUNT(*) as `count`
FROM `postfix_mails`
WHERE `queued_at` LIKE '2016-04-11%'
  AND `host` = 'mta03'

查询需要100到110毫秒。

目前该表包含大约70 000封邮件,查询返回大约31 000封。这只是几天的时间。值得邮件,我打算至少保留一个月。查询缓存没有多大帮助,因为表格不断更新。

我试过这样做:

SELECT SQL_NO_CACHE COUNT(*) as `count`
FROM `postfix_mails`
WHERE `queued_at` >= '2016-04-11'
  AND `queued_at` < '2016-04-12'
  AND `host` = 'mta03'

但查询需要完全相同的时间才能运行。我对MySQL配置进行了这些更改:

[mysqld]
query_cache_size = 128M
key_buffer_size = 256M

read_buffer_size = 128M
sort_buffer_size = 128M

innodb_buffer_pool_size = 4096M

并确认它们全部有效(SHOW VARIABLES),但查询运行速度不快。

我做了一些愚蠢的事情让这个查询需要这么久吗?您能否发现任何明显或非显而易见的方法来加快速度?在这种情况下,是否有另一个数据库引擎比InnoDB更好?

mysql> EXPLAIN SELECT SQL_NO_CACHE COUNT(*) as `count`
    -> FROM `postfix_mails`
    -> WHERE `queued_at` >= '2016-04-11'
    ->   AND `queued_at` < '2016-04-12'
    ->   AND `host` = 'mta03';
+----+-------------+---------------+------+--------------------------+----------------+---------+-------+-------+-------------+
| id | select_type | table         | type | possible_keys            | key            | key_len | ref   | rows  | Extra       |
+----+-------------+---------------+------+--------------------------+----------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | postfix_mails | ref  | mail_signature,queued_at | mail_signature | 92      | const | 53244 | Using where |
+----+-------------+---------------+------+--------------------------+----------------+---------+-------+-------+-------------+
1 row in set (0.00 sec)

4 个答案:

答案 0 :(得分:2)

queued_at是日期时间值。不要使用LIKE。这会将其转换为字符串,从而阻止使用索引并强制执行全表扫描。相反,您需要适当的索引并修复查询。

查询是:

SELECT COUNT(*) as `count`
FROM `postfix_mails`
WHERE `queued_at` >= '2016-04-11' AND `queued_at` < DATE_ADD('2016-04-11', interval 1 day) AND
      `host` = 'mta03';

然后你需要postfix_mails(host, queued_at)上的综合索引。 host列必须是第一个。

注意:如果您当前的版本在70,000封电子邮件中的数量为31,000,那么索引对此没什么帮助。但是,这将使代码在未来更具可扩展性。

答案 1 :(得分:1)

如果您的查询非常快,则需要实现它。

MySQL缺乏本地执行此操作的方法,因此您必须创建一个这样的表:

CREATE TABLE mails_host_day
        (
        host VARCHAR(30) NOT NULL,
        day DATE NOT NULL,
        mails BIGINT NOT NULL,
        PRIMARY KEY (host, day)
        )

并在postfix_mails的触发器中或偶尔使用脚本更新它:

INSERT
INTO    mails_host_day (host, day, mails)
SELECT  host, CAST(queued_at AS DATE), COUNT(*)
FROM    postfix_mails
WHERE   id > :last_sync_id
GROUP BY
        host, CAST(queued_at AS DATE)
ON DUPLICATE KEY
UPDATE  mails = mails + VALUES(mails)

这样,查询主机日条目就是单个主键搜索。

请注意,基于触发器的解决方案会影响DML性能,而基于脚本的解决方案会导致实际数据略少。

但是,如果将最新的实际数据与存储的结果合并,则可以改进基于脚本的解决方案的实际情况:

SELECT  host, day, SUM(mails) AS mails
FROM    (
        SELECT  host, day, mails
        FROM    mails_host_day
        UNION ALL
        SELECT  host, CAST(queued_at) AS day, COUNT(*) AS mails
        FROM    postfix_mails
        WHERE   id >= :last_sync_id
        GROUP BY
                host, CAST(queued_at) AS day
        ) q

不再是单个索引查找,但是,如果经常运行更新脚本,则会有更少的实际记录要读取。

答案 2 :(得分:0)

您在&#39;主机&#39;,&#39; mail_id&#39;和&#39;以及&#39;上有一个唯一的密钥,但是当查询引擎尝试使用该索引时,您不是& #39;过滤&#39; mail_id&#39;并且&#39;到&#39;,所以它可能效率不高。一个解决方案可能是在主机上添加另一个索引&#39;或者在查询中添加AND 'mail_id' IS NOT NULL AND'to' IS NOT NULL以充分利用现有的唯一索引。

答案 3 :(得分:0)

您可以使用分页来加速PHP中的查询,这通常是我解决包含大量数据的问题的方法 - 但这取决于您的表层次结构。

将您的LIMIT集成到SQL查询中。

<强> PHP:

foreach ($db->Prepare("SELECT COUNT(*) as `count`
FROM `postfix_mails`
WHERE DATEDIFF(`queued_at`, '2016-04-11') = 0)
AND mail_id < :limit "))->execute(array(':limit' => $_POST['limit'])) as $row)
{
    // normal output
}

<强> jQuery的:

$(document).ready( function() {
    var starting = 1;
    $('#next').click( function() {
        starting = starting + 10;
        $.post('phpfilehere.php', { limit: starting })
            .done( function(data) {
                $('#mail-output').innerHTML = data;
            });
    );

);

在这里,每个页面显示10封电子邮件,当然您可以更改并修改它,甚至添加一个搜索,我实际上有一个我用于所有项目的对象。

我只是认为我会分享这个想法 - 它也会在您的网站上添加实时数据流。

Facebook的滚动节目更让我受到启发 - 这真的不难,但却是查询大量数据的好方法。