我通过亚马逊使用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
需要这么长时间来计算?必须有一些我没有做好的事情。
任何建议都表示赞赏!
答案 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, ..., 24
和date, 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;
如果添加AVG()或其他内容,则会丢失此优化