我正在通过 Amazon can service 使用 MySQL 服务器,使用默认设置。所涉及的表mytable属于InnoDB类型,大约有 10 亿行。查询是:
select count(*), avg(`01`) from mytable where `date` = "2017-11-01";
Run Code Online (Sandbox Code Playgroud)
这需要将近 10 分钟的时间来执行。我在 上有一个索引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 |
+----+-------------+---------------+------+---------------+------+---------+-------+---------+-------+
Run Code Online (Sandbox Code Playgroud)
该表中的索引是:
+---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| 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 | | |
+---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
Run Code Online (Sandbox Code Playgroud)
如果我删除AVG():
select count(*) from mytable where `date` = "2017-11-01";
Run Code Online (Sandbox Code Playgroud)
返回计数只需要 0.15 秒。此特定查询的计数为 692792;其他dates的计数类似。
我没有索引01。这是一个问题吗?为什么AVG()需要这么长时间来计算?一定是我做错了什么。
任何建议表示赞赏!
要计算具有特定日期的行数,MySQL 必须在索引中定位该值(这非常快,毕竟这是为索引创建的),然后读取索引的后续条目,直到找到下一个日期。根据 的数据类型esi,这将总结为读取一些 MB 的数据来计算您的 700k 行。读取一些 MB 不会花费太多时间(并且该数据甚至可能已经缓存在缓冲池中,具体取决于您使用索引的频率)。
要计算未包含在索引中的列的平均值,MySQL 将再次使用索引查找该日期的所有行(与以前相同)。但另外,对于它找到的每一行,它必须读取该行的实际表数据,这意味着使用主键定位该行,读取一些字节,并重复 700k 次。这种“随机访问”是很多比在第一种情况下读取连续的慢。(由于“某些字节”是innodb_page_size(默认情况下为16KB)的问题,情况变得更糟,因此与 ; 的“某些 MB”相比,您可能需要读取高达 700k * 16KB = 11GB 的大小,count(*)并且根据您的内存配置,某些的这些数据可能不会被缓存,而必须从磁盘读取。)
对此的解决方案是在索引中包含所有使用的列(“覆盖索引”),例如在 上创建索引date, 01。然后 MySQL 不需要访问表本身,可以继续进行,类似于第一种方法,只需读取索引即可。索引的大小会增加一点,因此 MySQL 将需要读取“更多 MB”(并执行 -avg操作),但这仍然应该是几秒钟的事情。
在评论中,您提到您需要计算 24 列的平均值。如果要同时计算avg多个列的 ,则需要对所有列进行覆盖索引,例如date, 01, 02, ..., 24以防止表访问。请注意,包含所有列的索引需要与表本身一样多的存储空间(创建这样的索引需要很长时间),因此这可能取决于此查询的重要性是否值得这些资源。
为了避免每个索引 16 列的MySQL 限制,您可以将其拆分为两个索引(和两个查询)。创建例如索引date, 01, .., 12和date, 13, .., 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;
Run Code Online (Sandbox Code Playgroud)
确保很好地记录这一点,因为没有明显的理由以这种方式编写查询,但这可能是值得的。
如果您只对单列求平均值,则可以添加 24 个单独的索引(在date, 01, date, 02, ... . 但是缓冲池可能仍然支持完整索引,具体取决于使用模式和内存配置等因素,因此您可能需要对其进行测试。
由于date是主键的一部分,您还可以考虑将主键更改为date, esi. 如果您通过主键查找日期,则不需要额外的步骤来访问表数据(因为您已经访问了该表),因此行为将类似于覆盖索引。但这是对您的表的重大更改,可能会影响所有其他查询(例如用于esi定位行的查询),因此必须仔细考虑。
正如您所提到的,另一种选择是构建一个汇总表,您可以在其中存储预先计算的值,特别是如果您不添加或修改过去日期的行(或者可以使用触发器使它们保持最新)。
| 归档时间: |
|
| 查看次数: |
481 次 |
| 最近记录: |