[英]Is there any way to optimize this SELECT query any further?
我有一個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)
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,那么索引對此不會有太大幫助。 但是,這將使代碼在未來更具可擴展性。
如果您的查詢非常快,則需要實現它。
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
的觸發器中或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
它不再是單個索引查找,但是,如果經常運行更新腳本,將會有更少的實際記錄要讀取。
您在'host','mail_id'和'to'上有一個唯一鍵,但是當查詢引擎嘗試使用該索引時,您不會過濾'mail_id'和'to',因此它可能不是高效。 一個解決方案可能是在'host'上添加另一個索引,或者在你的查詢中添加AND 'mail_id' IS NOT NULL AND'to' IS NOT NULL
以充分利用現有的唯一索引。
您可以使用分頁來加速PHP中的查詢,這通常是我如何解決包含大量數據的任何內容 - 但這取決於您的表層次結構。
在SQL查詢中集成LIMIT
。
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的滾動節目更讓我受到了啟發 - 這真的不難,但卻是查詢大量數據的好方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.