使用`group by`

时间:2018-03-09 15:39:56

标签: mysql database indexing group-by myisam

以下是我的show create table表:

CREATE TABLE `tcm_myisam` (
  `time` int(10) unsigned NOT NULL,
  `asn` int(10) NOT NULL,
  `pop` char(3) NOT NULL,
  `country` char(2) NOT NULL,
  `requests` float DEFAULT NULL,
  `rtt` float DEFAULT NULL,
  `rexb` float DEFAULT NULL,
  `nae` float DEFAULT NULL,
  `nf` float DEFAULT NULL,
  `override` float DEFAULT NULL,
  PRIMARY KEY (`time`,`asn`,`pop`,`country`),
  KEY `tcm_asn_country_idx` (`asn`,`country`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8

该表是一个日志。每隔5分钟,我运行一个脚本向该表中添加大约500,000行,每行由(time, asn, pop, country)唯一键控。对于给定的asn, pop, country三元组,我每次运行脚本时都会计算几个度量标准,然后将这些度量标准转储到表中。以这种方式附加到表后,行永远不会被修改 - 尽管我删除了超过90天的数据。

每5分钟大约500,000行,经过整整90天我们已经收集了:

12 (runs per hour) * 24 (hours) * 90 (days) * 500000 (rows) = 13 BILLION rows

由于索引,一些(相当复杂的)查询运行得非常快,尽管行数很多:

select
    time,
    coalesce(sum(rtt*requests)/sum(requests), 0) as avg_rtt,
    coalesce(sum(rexb*requests)/sum(requests), 0) as avg_rexb,
    coalesce(sum(nae*requests)/sum(requests), 0) as avg_nae,
    coalesce(sum(nf*requests)/sum(requests), 0) as avg_nf,
    coalesce(sum(override*requests)/sum(requests), 0) as avg_override
from
    tcm_myisam
where
    asn = 7018 and
    country = "US"
group by
    time, asn, country
order by time asc;

25920 rows in set, 4012 warnings (15.55 sec)

有些问题甚至是即时的:

select distinct(time) from tcm_myisam;

25920 rows in set (0.00 sec)

但是,这个特定查询运行的 批次 比我认为的要慢:

select time, count(*) from tcm_myisam group by time;

25920 rows in set (25 min 55.87 sec)

有谁知道为什么这么慢?

更新

以下是查询速度非常慢的EXPLAIN

mysql> explain select time, count(*) from tcm_myisam group by time;
+----+-------------+------------+------------+-------+---------------+---------+---------+------+-------------+----------+-------------+
| id | select_type | table      | partitions | type  | possible_keys | key     | key_len | ref  | rows        | filtered | Extra       |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+-------------+----------+-------------+
|  1 | SIMPLE      | tcm_myisam | NULL       | index | PRIMARY       | PRIMARY | 23      | NULL | 13343405769 |   100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+---------+---------+------+-------------+----------+-------------+

看起来它正在使用索引(按Using index位),但它仍然运行得非常慢。由于我的主键最左边的列是time,这应该是一个简单的陈述

对@RickJames的回复

注意: @RickJames修改了他的帖子以回应这个问题。请参阅"编辑:"他的帖子的部分细节。

由于我想发布大量的回复,我无法将其纳入评论。因此,我已就你在答案中提出的每一点进行了修改。

  

使用InnoDB,而不是MyISAM

我实际上有两个单独的表,因为我正在执行性能实验 - tcm_myisamtcm_innodb

那就是说,考虑MyISAM的决定并不是一个轻浮的决定。 InnoDB在MyISAM之上提供了很多的功能,我不需要这些功能:

  • 参照完整性 - 我的表格中没有外键
  • 事务/原子性 - 我不使用事务,写入失败期间损坏的数据不会对我的用例产生负面影响
  • 行锁定 - 只有一个脚本写入表,脚本永远不会同时运行多次,并且它只会追加或删除行(从不修改它们)。因此,我没有从行锁定中获益
  • 回滚 - 由于我不使用交易,因此我不使用此功能

由于MyISAM表提供较小的磁盘空间(从磁盘读取的数据较少)并提供更简单的事务模型,因此减少了查询开销。一般的建议是#34;如果你执行大量的阅读,MyISAM 可能更快。如果你执行大量写操作,InnoDB 总是更快"。我碰巧陷入了MyISAM优于InnoDB的少数用例之一。

在我的测试中,相当复杂的"查询在给定的ASN和国家/地区的所有时间聚合多个指标,在MyISAM上运行大约15秒,在InnoDB上运行大约20秒。

  

[获取]摆脱二级索引

建议这样做的唯一原因是"软化打击" InnoDB的桌面尺寸较大。通常,如果您根据列进行分组或选择,则最好对其进行索引。告诉我要消除这个与我分组的列完全匹配的索引是asinine。

  

将此[查询]更改为此[查询]

我(显然是错误的)认为,为了出现在where子句中,列必须是group by子句的一部分。但是,两个查询都在相同的时间内执行。你的版本只有几个字符更简洁 - 零性能增益

  

并将索引更改为[此顺序]

我在此处发布的查询并不是对数据执行的唯一查询。对数据运行的最常见查询是返回给定时间内的所有数据 - 因此,出于群集原因,将time作为主索引中的第一列是有意义的。我还同时附加给定time的所有数据,并执行常规数据库维护以修剪早于某个time的所有数据。由于我对数据库的唯一写入是按时间进行聚类,因此以任何其他方式对数据进行聚类是没有意义的。

事实上,那个人“非常慢”#34;我在这里发布的查询是从这个在给定时间内选择所有数据的常见用例中诞生的。我需要估算这些基于时间的组的文件大小,所以我想知道每次有多少行。

通过将我的主键更改为(asn, country, time, pop),它可以适度地提高"相当复杂的"查询我发布了,但它会破坏我的大多数其他查询的性能

  

您是否故意使用NULL

在指标收集时,某些指标可能无法使用。要么是因为我的一个数据源无法返回数据,要么是因为我们此时没有特定ASN +国家/流行对的数据。如果我们没有任何指标的数据(如果我们无法计算rttrexbnfnae override)然后我们不为该ASN + country + pop插入一行。但是,如果我们至少有一个指标(可能我们有足够的数据来计算rtt但不足以计算nae),那么我们会使用NULL填写缺失的列

如果我们只是将NULL列替换为0之类的内容,那么我们就有可能低估我们的平均值

  

我不认为sum(rtt*requests)/sum(rtt)是" avg_rtt"

好抓 - 这是一个错字

  

不要在country

上使用utf8

我在创建表时实际上并没有指定charset(这是MySQL分配的默认值,当我输入show create table tcm_myisam时出现在输出中)

我会尝试更改字符集,但我不会预料到性能会发生有意义的变化

  

慢速查询

select distinct(time) from tcm_mysiam;

需要0.00秒,因为我的数据是按时间聚集和索引的,所以它能够从元数据表中回答查询,而不是执行表扫描

select time, count(*) from tcm_myisam group by time;
如果我的理解是正确的,

也应该能够使用这些元数据表 - 但事实并非如此

  

90天后删除

到目前为止,我自1月初以来一直只收集数据,所以我们还没有完整的90天数据(这意味着"删除"声明还没有&#t; t之前已在数据库上运行)。为了测试性能,当我达到约130亿行时,我运行了一个脚本来在测试数据库上生成虚假数据。

我的印象是,通过将time作为我的主键(因此按时间聚类),删除操作会很快。但是,当时机成熟时,我会将分区视为提高性能的额外步骤。

  

摘要表

此摘要表已存在。存在500k行的批次,以便我们可以深入了解这些摘要的计算方式。

例如,如果汇总表显示:"印度三天前下午5点RTT出现飙升",我们可以在三天前下午5点深入了解印度的所有数据,以找出哪一个ASN或持久性有机污染物受到影响。

附录:我目前有两个汇总表。一个返回每个国家/地区所有指标的最小值,最大值和加权平均值(汇总所有ASN和POP值)。一个返回每个ASN的所有指标的最小值,最大值和加权平均值(汇总所有国家/地区和POP值)。实际上,这些汇总表会缩小我的键:

(time, asn, country, pop) -> (time, country)
(time, asn, country, pop) -> (time, asn)

我没有添加"行数"到这些汇总表。因此,通过添加,我可以使用摘要表比使用原始表更快地获得每次总计数。

此外,我没有一个汇总表,可以在给定时间内返回有意义的数据:

(time, asn, country, pop) -> (time)

这样的表不仅可以包括"行数"还可以包括"超过某个阈值的行数"或"不同ASN的数量"。所以我会添加这样一个表并调整我的应用程序,以便在适当的时候从中读取。

  

非常缓慢

我很清楚,阅读所有130亿行需要时间。即使在连接到专用PCI-e 3.0x4线路(大约32 GB / s带宽)的M.2 SSD上,我们也只需要5-8秒来读取磁盘上的主键.... #39; s 如果我们正在阅读所有130亿行

我的索引的目标是避免一次读取所有130亿行。所有130亿行必须可用(我们应该选择阅读它们),但我们一次只读取最多500,000行(当我们要求"所有数据"在给定时间内)。因此,我们不会阅读130亿个主键,而是阅读26000" time"用于过滤我们实际需要的500,000行的键,然后读取那些500,000行。从磁盘读取的总共526,000行(索引+数据)和磁盘I / O减少5-6个数量级。

在大多数情况下,这很有效。我当然没有在专用的PCI-e 3.0x4系列上安装M.2 SSD。我在共享SATA线路上有一个糟糕的盘片磁盘,它同时被同一台机器上运行的其他应用程序写入和读取。我很幸运能看到50 MB / s的读取速度。尽管如此,我看到查询在1分钟内完成(通常)。

然而,select time, count(*)查询困扰了我,因为我认为这会利用我的索引,而是扫描整个表格(导致我的糟糕磁盘执行时间<25分钟)

所以我原来的问题的关键是:

使用count(*)时,如何使用group by查询来获取性能指标?

请注意,更简单的查询select count(*) from tcm_myisam会利用表格元数据并立即返回。

1 个答案:

答案 0 :(得分:1)

架构和查询更改

使用InnoDB,而不是MyISAM。这将导致磁盘占用空间显着增加;下面,我建议摆脱二级指数,这将减轻打击。不过,足迹可能是两倍大。

编辑:InnoDB的原因:(1)安全崩溃,(2)PK的效率。虽然有更多的开销和#34;在InnoDB中,过去十年中所有的性能改进都是针对InnoDB的。因此,InnoDB通常尽可能快或更快,尽管有&#34;开销&#34;。我想知道在添加索引建议后InnoDB是否会继续超越MyISAM。

更改此

where
    asn = 7018 and
    country = "US"
group by
    time, asn, country
order by time asc;

到此:

WHERE asn = 7018
  AND country = "US"
GROUP BY time
ORDER BY time ASC;

将索引更改为

PRIMARY KEY(asn, country, time, pop)  -- in this order

编辑:&#34;消除与列完全匹配的索引&#34; - 由于PK是一个索引,我没有消除索引。此外,由于PK是&#34;聚集&#34;使用数据,查询在InnoDB中本质上比MyISAM运行得更快。 (MyISAM必须在PK BTree和数据之间来回反复; InnoDB不需要。)

编辑:我从asn删除了countryGROUP BY,以便GROUP BYORDER BY可以相同,从而避免额外的分类。 (它与WHERE无关,只是注意到这两列是使用=进行测试的,因此在GROUP BY中无关紧要。)

编辑:&#34;我在此处发布的查询并不是对数据执行的唯一查询。&#34; - 嗯,我也无法完成帮助,直到我看到它们为止。我已经为提供的查询提供了建议。其他查询可能会或可能不会帮助或伤害我的建议。

编辑&#34;有时间成为我的主索引中第一列的聚类原因#34; - 是和否。&#39;是&#39;,如果主要活动是INSERTing。 &#39;无&#39;如果主要活动是SELECTing和/或群集提供了显着的性能提升。

现在25920 rows in set, 4012 warnings (15.55 sec)的运行速度会明显加快。但是你也应该用

检查警告
SHOW WARNINGS LIMIT 20;

您是否故意使用NULL?或者列可以是NOT NULL吗?算术会搞砸吗?

我不认为sum(rtt*requests)/sum(rtt)是&#34; avg_rtt&#34;。也许除以sum(requests) ??

不要在country使用utf8;也许不适用于pop

编辑:在某些版本/引擎中,需要6个字节。更大的表 - &gt;较慢的查询(有点)。

慢速查询

select distinct(time) from tcm_myisam;
由于MyISAM,或者因为您打开了查询缓存,

花了0.00秒。它可能应该关闭,因为插入物每5分钟清除一次现金。

编辑:我很好奇。你能提供EXPLAIN select ...吗?同时使用select SQL_NO_CACHE ...计时以避免质量控制。可能有SELECT DISTINCT的优化超越了索引。

select time, count(*) from tcm_myisam group by time;

需要进行表扫描,因此它注定会很慢,并且随着表的增长而变慢。我稍后会解决一个问题。

90天后删除

你测试过这个吗?你看到它有多贵吗?让我们通过PARTITIONing来解决这个问题。我建议PARTITION BY RANGE(TO_DAYS(time))。那将需要大约16个分区。您每周DROP PARTITION一次,REORGANIZE每周一次。详情请见http://mysql.rjweb.org/doc.php/partitionmaint

这将使&#34;删除&#34;瞬间。它会减慢原始查询的速度,但我认为权衡是值得的。减速的原因是必须从16个分区中的每个分区中获取一些行。

编辑:&#34;删除会很快[如果time是第一个]&#34; - 它变得更复杂。在MyISAM中,一个巨大的洞将被刻入数据中。这个洞将由后续的INSERTs填写,直到下一个&#34;删除&#34;。随着时间的推移,MyISAM表将变得越来越分散。使用InnoDB,还会有一个&#34;漏洞&#34;但基本上没有&#34;碎片&#34;。在这两种情况下,表都不会缩小;只有自由空间。是的,如果PK time启动,则删除速度会比我建议的PK快一些。但是DROP PARTITION会比DELETE快得多。

编辑:&#34;也应该能够使用这些元数据表&#34; - 唯一接近&#34;元数据&#34;是MyISAM保留行数。对于COUNT(*)WHEREGROUP BY肯定会更好。但仅针对该查询

编辑:&#34;我们读了26000&#34;时间&#34;用于过滤我们实际需要的500,000行的键#34; - 请注意PARTITION BY (TO_DAYS(time))除{/ 1}}之外的其他任何内容(例如WHERE time BETWEEN .. AND ..)外,还允许粗略WHERE 。也就是说,分区给出了二维索引的粗略近似。所以...尽管我从PK的开头移动asn,你仍然不需要读取130亿行来获得短时间范围。任何过滤到一周以下的查询只能打1个或2个分区(取决于时间范围与分区的对齐),因此只有1或20亿行,而不是13。

汇总表

通常,在像这样的数据仓库情况下,构建和维护&#34;汇总表&#34;显着提升性能(可能是10倍)。

在您的情况下,不是(或除了)将500K 原始行投入Fact表,而是汇总它们并将它们放入另一个表中。然后针对该表执行time

不理解为什么每批中有500K行,我不能更具体。

汇总表的一些通用信息:http://mysql.rjweb.org/doc.php/summarytables

编辑:&#34;汇总所有时间内的多个指标&#34; - 摘要表的主要原因。

非常缓慢

13 billlion 行(PK为200GB?)需要时间来读取。它将受I / O限制。我的更改将使查询运行得更慢;但这是一个重要的问题吗?合适的汇总表可以更快地获得计数。