如何使用包含多个日期的案例语句和GROUP的多个SUMS优化mysql查询?

时间:2017-04-11 22:03:58

标签: php mysql sum case query-optimization

我正在尝试优化一个mysql查询,该查询具有多个总和,每个总和具有不同的case语句,并且可以按多个日期分组,或者每次循环运行查询的多个日期。当前查询执行每次运行需要1.8 - 3.2秒。

目前,我每次都会循环运行查询的30多个日期,即使是在快速一侧(每个查询1.8秒),也就是运行查询30次的54秒。

首先,我认为如果我可以在给定的日期范围内拥有查询组,这将有助于优化开始,但我不确定按给定日期范围进行分组的最佳方式。

其次,我确信我的表和/或查询本身可以进行优化。

我提供了SHOW CREATE TABLE详细信息,查询的一个示例,以及调用查询30次的php循环。如果还有其他任何可以帮助的地方,请询问。我感谢帮助和反馈:)

表详细信息:

mysql> SHOW CREATE TABLE accounts ;

    accounts | CREATE TABLE `accounts` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `verified` tinyint(1) DEFAULT '0',
  `active` int(1) unsigned NOT NULL DEFAULT '1',
  `clear` tinyint(1) unsigned DEFAULT '0',
  `email` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `batch` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `batch_start` datetime DEFAULT NULL,
  `batch_complete` datetime DEFAULT NULL,
  `auth_failed_updated` datetime DEFAULT NULL,
  `checking_start` datetime DEFAULT NULL,
  `checking_complete` datetime DEFAULT NULL,
  `last_used` datetime DEFAULT NULL,
  `last_tested` datetime DEFAULT NULL,
  `creation_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  KEY `batch_start` (`batch_start`),
  KEY `batch_complete` (`batch_complete`),
  KEY `last_used` (`last_used`),
  KEY `last_tested` (`last_tested`),
  KEY `active` (`active`),
  KEY `auth_failed_updated` (`auth_failed_updated`)
) ENGINE=MyISAM AUTO_INCREMENT=1422229 DEFAULT CHARSET=latin1

单一查询示例:

mysql>     SELECT  date(now()) as date,
        SUM(CASE WHEN date(acct.batch_start) = date(now()) THEN 1 ELSE 0 END) AS batch_start_count,
        SUM(CASE WHEN date(acct.batch_complete) = date(now()) THEN 1 ELSE 0 END) AS batch_complete_count,
        SUM(CASE WHEN (acct.batch_start IS NOT NULL
                      AND  acct.batch_complete IS NULL
                      AND  acct.active = 0
                      AND  date(acct.auth_failed_updated) = date(now()) ) THEN 1 ELSE 0 END
           ) AS batch_died_count,
        SUM(CASE WHEN (acct.batch = 3
                      AND  acct.last_used IS NULL
                      AND  date(acct.last_tested) = date(now())
                      AND  acct.last_used IS NULL) THEN 1 ELSE 0 END
           ) AS batch_died_unused_count,
        SUM(CASE WHEN (acct.batch = 3
                      AND  date(acct.last_used) = date(now())
                      AND  acct.active = 0) THEN 1 ELSE 0 END
           ) AS batch_died_used_count
    FROM  accounts acct
    GROUP BY  date
    ORDER BY  date ASC;

+------------+--------------------+-----------------------+-------------------+-------------------------+----------------------+
| date       | batch_start_count  | batch_complete_count  | batch_died_count  | batch_died_unused_count | batch_died_used_count |
+------------+--------------------+-----------------------+-------------------+-------------------------+----------------------+
| 2017-04-11 |               4040 |                   847 |              1856 |                      0  |                 1327 |
+------------+--------------------+-----------------------+-------------------+-------------------------+----------------------+
1 row in set (2.44 sec)

EXPLAIN QUERY:

mysql> EXPLAIN (of that query)

+----+-------------+-------+------+---------------+------+---------+------+---------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------+
|  1 | SIMPLE      | acct  | ALL  | NULL          | NULL | NULL    | NULL | 1421996 |       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------+

PHP代码在过去30天内循环:

$dates = array();
for($i = -30; $i < 1; $i++) {
    $the_date = date("Y-m-d", strtotime('-'. $i .' days ago'));
    $dates[] = $the_date ;
    $sql = "SELECT date('{$the_date}') as date, SUM(CASE WHEN date(acct.batch_start) = date('{$the_date}') THEN 1 ELSE 0 END) AS batch_start_count, SUM(CASE WHEN date(acct.batch_complete) = date('{$the_date}') THEN 1 ELSE 0 END) AS batch_complete_count, SUM(CASE WHEN (acct.batch_start IS NOT NULL AND acct.batch_complete IS NULL AND acct.active = 0 AND date(acct.auth_failed_updated) = date('{$the_date}') ) THEN 1 ELSE 0 END) AS batch_died_count, SUM(CASE WHEN (acct.batch = 3 AND acct.last_used IS NULL AND date(acct.last_tested) = date('{$the_date}') AND acct.last_used IS NULL) THEN 1 ELSE 0 END) AS batch_died_unused_count, SUM(CASE WHEN (acct.batch = 3 AND date(acct.last_used) = date('{$the_date}') AND acct.active = 0) THEN 1 ELSE 0 END) AS batch_died_used_count FROM accounts acct LEFT JOIN networks net ON net.id = acct.networks_id LEFT JOIN servers srv ON srv.id = net.servers_id GROUP BY date ORDER BY date ASC;" ; 
    if ($result = $mysqli->query($sql)) {
        $row = $result->fetch_assoc() ; 
        print_r($row) ;
        print "<br>" ; 
    }
}

如果您对如何提高性能有任何见解,我将非常感激。感谢您花时间看看这个!

1 个答案:

答案 0 :(得分:0)

计划A:

  1. 使用您需要的日期构建一个小表。
  2. JOIN到该表,将date(now())的所有实例替换为该表中的日期。
  3. 其余代码可能没问题。

    另一个次要优化是要注意这些值给出相同的值:

     SUM(CASE WHEN boolean_expr) THEN 1 ELSE 0 END
     SUM(          boolean_expr)
    

    这是因为TRUE被视为1

    B计划:

    CREATE TEMPORARY TABLE t
        SELECT  DATE(batch_start) AS batch_start,  -- Note this may stay NULL
                DATE( ... etc  ...
                active,
                batch_complete IS NULL AS incomplete,
                batch
            FROM accounts;
    

    然后在稍微简化的查询中使用它来完成剩下的工作。我不知道这是否会有所帮助,但它可能会使查询更具可读性。