MySQL服务器上非常简单的AVG()聚合查询花费了很长时间

时间:2018-03-21 02:50:29

标签: mysql aggregation

我通过亚马逊使用MySQL服务器可以使用默认设置进行维修。涉及的/v1/payments/sale/{sale_id}表格为mytable类型,大约有10亿行。 查询是:

InnoDB

执行需要将近10分钟。我有select count(*), avg(`01`) from mytable where `date` = "2017-11-01"; 的索引。此查询的date为:

EXPLAIN

此表中的索引为:

+----+-------------+---------------+------+---------------+------+---------+-------+---------+-------+
| id | select_type | table         | type | possible_keys | key  | key_len | ref   | rows    | Extra |
+----+-------------+---------------+------+---------------+------+---------+-------+---------+-------+
|  1 | SIMPLE      | mytable       | ref  | date          | date | 3       | const | 1411576 | NULL  |
+----+-------------+---------------+------+---------------+------+---------+-------+---------+-------+

如果我删除+---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | +---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | mytable | 0 | PRIMARY | 1 | ESI | A | 60398679 | NULL | NULL | | BTREE | | | | mytable | 0 | PRIMARY | 2 | date | A | 1026777555 | NULL | NULL | | BTREE | | | | mytable | 1 | lse_cd | 1 | lse_cd | A | 1919210 | NULL | NULL | YES | BTREE | | | | mytable | 1 | zone | 1 | zone | A | 732366 | NULL | NULL | YES | BTREE | | | | mytable | 1 | date | 1 | date | A | 85564796 | NULL | NULL | | BTREE | | | | mytable | 1 | ESI_index | 1 | ESI | A | 6937686 | NULL | NULL | | BTREE | | | +---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

AVG()

返回计数只需0.15秒。此特定查询的计数为692792;其他select count(*) from mytable where `date` = "2017-11-01"; 的计数相似。

我没有date的索引。这是一个问题吗?为什么01需要这么长时间来计算?必须有一些我没有做好的事情。

任何建议都表示赞赏!

2 个答案:

答案 0 :(得分:3)

要计算具有特定日期的行数,MySQL必须在索引中找到该值(这非常快,毕竟这是为索引制作的),然后读取后续条目索引直到找到下一个日期。根据{{​​1}}的数据类型,这将总结为读取一些MB数据来计算您的700k行。读取一些MB并不需要花费太多时间(并且数据甚至可能已经缓存在缓冲池中,具体取决于您使用索引的频率)。

要计算未包含在索引中的列的平均值,MySQL将再次使用索引查找该日期的所有行(与之前相同)。但另外,对于它找到的每一行,它必须读取该行的实际表数据,这意味着使用主键来定位行,读取一些字节,并重复这700k次。这个"random access" 很多比第一种情况下的顺序读取慢。 (由于"某些字节"是innodb_page_size(默认为16KB)的问题,这会变得更糟,因此与"相比,您可能需要读取高达700k * 16KB = 11GB的内容。某些MB"用于esi;并且根据您的内存配置,某些数据可能不会被缓存,必须从磁盘读取。)

对此的解决方案是在索引中包括所有使用的列("覆盖索引"),例如在count(*)上创建索引。然后MySQL不需要访问表本身,并且可以通过读取索引继续,类似于第一种方法。索引的大小会有所增加,因此MySQL需要阅读更多的MB" (并执行date, 01 - 操作),但它应该只需几秒钟。

在评论中,您提到需要计算超过24列的平均值。如果您想同时为多个列计算avg,则需要对所有列进行覆盖索引,例如: avg以阻止表访问。请注意,包含所有列的索引需要与表本身一样多的存储空间(并且创建此类索引需要很长时间),因此它可能取决于此查询在值这些资源时的重要性。

要避免MySQL-limit of 16 columns per index,您可以将其拆分为两个索引(和两个查询)。创建例如索引date, 01, 02, ..., 24date, 01, .., 12,然后使用

date, 13, .., 24

确保记录得很好,因为没有明显的理由以这种方式编写查询,但它可能是值得的。

如果您只对一列进行平均,则可以添加24个单独的索引(select * from (select `date`, avg(`01`), ..., avg(`12`) from mytable where `date` = ...) as part1 cross join (select avg(`13`), ..., avg(`24`) from mytable where `date` = ...) as part2; date, 01,...),但总的来说,它们需要更多空间,但是可能会快一点(因为它们个别较小)。但缓冲池可能仍然支持完整索引,具体取决于使用模式和内存配置等因素,因此您可能需要对其进行测试。

由于date, 02是主键的一部分,您还可以考虑将主键更改为date。如果您通过主键找到日期,则不需要额外的步骤来访问表数据(因为您已经访问过表),因此行为类似于覆盖索引。但这对您的表格进行了重大更改,并且可能会影响所有其他查询(例如,使用date, esi查找行),因此必须仔细考虑。

正如您所提到的,另一种选择是构建一个汇总表,您可以在其中存储预先计算的值,尤其是如果您不为过去的日期添加或修改行(或者可以使用触发器使它们保持最新)。 / p>

答案 1 :(得分:0)

对于MyISAM表,如果SELECT从一个表中检索,没有检索到其他列,并且没有WHERE子句,则COUNT(*)被优化为非常快速地返回。

例如:

  

SELECT COUNT(*)FROM student;

amir133

如果添加AVG()或其他内容,则会丢失此优化