mysql - 按索引列分组+由索引列引起的速度降低

时间:2012-01-08 14:08:46

标签: mysql indexing group-by innodb

我有下一个结构的表statistics

+-------------------+----------------------+------+-----+---------+----------------+
| Field             | Type                 | Null | Key | Default | Extra          |
+-------------------+----------------------+------+-----+---------+----------------+
| id                | int(11)              | NO   | PRI | NULL    | auto_increment |
| created_at        | datetime             | YES  | MUL | NULL    |                |
| year_in_tz        | smallint(5) unsigned | YES  | MUL | NULL    |                |
| month_in_tz       | tinyint(3) unsigned  | YES  | MUL | NULL    |                |
+-------------------+----------------------+------+-----+---------+----------------+

使用created_at,year_in_tz,month_in_tz和on(year_in_tz,month_in_tz)上的键:

 ALTER TABLE `statistics` ADD INDEX created_at (created_at);
 alter table statistics add index year_in_tz (year_in_tz);
 alter table statistics add index month_in_tz (month_in_tz);
 alter table statistics add index year_month_in_tz(year_in_tz,month_in_tz);

一些查询示例......

mysql> SELECT COUNT(*) AS count_all, year_in_tz, month_in_tz 
       FROM `statistics` 
       GROUP BY year_in_tz, month_in_tz;
+-----------+------------+-------------+
| count_all | year_in_tz | month_in_tz |
+-----------+------------+-------------+
|    467890 |       2011 |          11 |
|   7339389 |       2011 |          12 |
+-----------+------------+-------------+
2 rows in set (5.04 sec)  

mysql> describe SELECT COUNT(*) AS count_all, year_in_tz, month_in_tz FROM `statistics` GROUP BY year_in_tz, month_in_tz;
 +----+-------------+--------------------+-------+---------------+------------------+---------+------+---------+-------------+
 | id | select_type | table              | type  | possible_keys | key              | key_len | ref  | rows    | Extra       |
 +----+-------------+--------------------+-------+---------------+------------------+---------+------+---------+-------------+
 |  1 | SIMPLE      | statistics         | index | NULL          | year_month_in_tz | 5       | NULL | 7797984 | Using index |
 +----+-------------+--------------------+-------+---------------+------------------+---------+------+---------+-------------+
 1 row in set (0.01 sec)

 mysql> SELECT COUNT(*) AS count_all, year_in_tz, month_in_tz 
        FROM `statistics` 
        WHERE (created_at BETWEEN '2011-10-31 20:00:00' AND '2011-12-31 19:59:59') 
        GROUP BY year_in_tz, month_in_tz;
 +-----------+------------+-------------+
 | count_all | year_in_tz | month_in_tz |
 +-----------+------------+-------------+
 |    467890 |       2011 |          11 |
 |   7339389 |       2011 |          12 |
 +-----------+------------+-------------+
 2 rows in set (1 min 33.46 sec)

 mysql> describe SELECT COUNT(*) AS count_all, year_in_tz, month_in_tz FROM `statistics` WHERE (created_at BETWEEN '2011-10-31 20:00:00' AND '2011-12-31 19:59:59') GROUP BY year_in_tz, month_in_tz;
 +----+-------------+--------------------+-------+---------------+------------------+---------+------+---------+-------------+
 | id | select_type | table              | type  | possible_keys | key              | key_len | ref  | rows    | Extra       |
 +----+-------------+--------------------+-------+---------------+------------------+---------+------+---------+-------------+
 |  1 | SIMPLE      | statistics         | index | created_at    | year_month_in_tz | 5       | NULL | 7797984 | Using where |
 +----+-------------+--------------------+-------+---------------+------------------+---------+------+---------+-------------+
 1 row in set (0.07 sec)

因此,如果我通过索引列对索引列+组使用where语句with子句,则速度极低。 也许有人知道如何改进上次查询以使其更快

PS 在使用索引后,我发现(created_at,year_in_tz,month_in_tz)上的新索引使查询运行得更快,但我希望每个查询0-1秒,而不是10秒:

alter table lending_statistics add index created_at_with_year_and_month_in_tz (created_at,year_in_tz,month_in_tz);

mysql> describe SELECT COUNT(*) AS count_all, year_in_tz, month_in_tz FROM `statistics`        WHERE (created_at BETWEEN '2011-10-31 20:00:00' AND '2011-12-31 19:59:59') GROUP BY year_in_tz, month_in_tz;
+----+-------------+--------------------+-------+-------------------------------------------------+--------------------------------------+---------+------+---------+-----------------------------------------------------------+
| id | select_type | table              | type  | possible_keys                                   | key                                  | key_len | ref  | rows    | Extra                                                     |
+----+-------------+--------------------+-------+-------------------------------------------------+--------------------------------------+---------+------+---------+-----------------------------------------------------------+
|  1 | SIMPLE      | statistics         | range | created_at,created_at_with_year_and_month_in_tz | created_at_with_year_and_month_in_tz | 9       | NULL | 3612208 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+--------------------+-------+-------------------------------------------------+--------------------------------------+---------+------+---------+-----------------------------------------------------------+

1行(0.05秒)

mysql> SELECT COUNT(*) AS count_all,        year_in_tz, month_in_tz        FROM `lending_statistics`        WHERE (created_at BETWEEN '2011-10-31 20:00:00' AND '2011-12-31 19:59:59')        GROUP BY year_in_tz, month_in_tz;
+-----------+------------+-------------+
| count_all | year_in_tz | month_in_tz |
+-----------+------------+-------------+
|    467890 |       2011 |          11 |
|   7339389 |       2011 |          12 |
+-----------+------------+-------------+
2 rows in set (10.62 sec)

3 个答案:

答案 0 :(得分:1)

将字段ID添加到索引created_at_with_year_and_month_in_tz,然后将select语句更改为

select count(id) ....

在MySQL 5.6中,ICP功能在这种情况下可能有所帮助,因为访问的所有字段都是索引的一部分。我相信当你指定count(*)时MySQL可能会读取实际的数据记录,因此它需要读取索引文件和数据文件。

答案 1 :(得分:0)

试试这个,有known MySQL issue日期时间索引

    WHERE
        created_at BETWEEN 
               CAST('2011-10-31 20:00:00' AS datetime) AND 
               CAST('2011-12-31 19:59:59'  AS datetime)

答案 2 :(得分:0)

COUNT(*)次查询是MySQL和Linux的常见问题。 PostgreSQL(和其他RDBMS),因为在查询执行期间执行了序列表扫描。尝试考虑在其他地方缓存汇总数据:memcachedredis等。