我有一个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)
答案 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的滚动节目更让我受到启发 - 这真的不难,但却是查询大量数据的好方法。