簡體   English   中英

有沒有辦法進一步優化這個SELECT查詢?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM