MyISAM table `count` 和 `group by` 非常慢
MyISAM table very slow for `count` with `group by`
以下是我的show create table
我的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
table是一个日志。每 5 分钟我 运行 一个脚本来向这个 table 添加大约 500,000 行,每行由 (time, asn, pop, country)
唯一键控。对于给定的 asn, pop, country
三元组,每次脚本 运行 时我都会计算几个指标,然后将这些指标转储到 table。以这种方式附加到 table 之后,这些行永远不会被修改——尽管我确实删除了超过 90 天的数据。
在整整 90 天后,我们收集了大约每 5 分钟 500,000 行:
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 修改了他的 post 以回应此。有关详细信息,请参阅他 post 的 "Edit:" 部分。
由于我想 post 作为回应的数量很大,所以我无法将其放入评论中。因此,我针对您在回答中提出的每一点修改了我的 post。
Use InnoDB, not MyISAM
我实际上有两个独立的 tables,因为我正在执行性能实验 -- tcm_myisam
和 tcm_innodb
。
也就是说,考虑 MyISAM 的决定并不是轻率的。 InnoDB 提供了 很多 的功能,超越了 MyISAM,none 我需要:
- 参照完整性 -- 我的 table
中没有外键
- T运行sactions / atomicity -- 我不使用 t运行sactions,写入失败期间损坏的数据不会对我产生负面影响用例
- 行锁定 -- 只有一个脚本写入 table,该脚本永远不会同时 运行ning 超过一次,并且它只会追加或删除行(从不修改它们)。因此我没有从行锁定中受益
- Rollbacks -- 由于我不使用 t运行saction,所以我不使用此功能
因为 MyISAM tables 提供更小的磁盘占用空间(从磁盘读取的数据更少)并提供更简单的 t运行saction 模型,查询开销减少。一般建议是 "if you perform a lot of reads, MyISAM may be faster. If you perform a lot of writes, InnoDB is always faster"。我碰巧属于 MyISAM 优于 InnoDB 的少数用例之一。
在我的测试中,"rather complex" 查询针对给定的 ASN 和国家 运行 聚合了所有时间的多个指标,在 MyISAM 上大约需要 15 秒,在 InnoDB 上大约需要 20 秒。
[Get] rid of the secondary index
建议这样做的唯一原因是 "soften the blow" InnoDB 的更大 table 大小。一般来说,如果您根据列进行分组或选择,最好在其上建立索引。告诉我删除与我分组所依据的列完全匹配的索引是愚蠢的。
Change this [query] to this [query]
我(显然是错误的)相信为了出现在 where
子句中,列必须是 group by
子句的一部分。但是,这两个查询的执行时间相同。您的版本只更简洁几个字符 - 性能增益为零
And change the indexes to [this order]
我在此处 post 进行的查询并不是对数据执行的唯一查询。最常见的数据查询 运行 是 return 给定时间的所有数据 - 因此出于集群原因,将 time
作为我的主索引中的第一列是有意义的。我还同时附加给定 time
的所有数据,并执行定期数据库维护以 p运行e 所有早于某个 time
的数据。由于我对数据库所做的唯一写入是按时间聚类的,因此以任何其他方式对数据进行聚类是没有意义的。
事实上,我在此处 post 编辑的 "absurdly slow" 查询是从这种选择给定时间的所有数据的常见用例中诞生的。我需要估计这些基于时间的组的文件大小,所以我要计算每次有多少行。
通过将我的主键更改为 (asn, country, time, pop)
它可能会适度提高我 post 编辑的 "rather complex" 查询的性能,但它会破坏我的大多数其他查询的性能
Are you deliberately using NULL
?
在收集指标时,某些指标可能不可用。要么是因为我的一个数据源无法 return 数据,要么是因为我们目前没有特定 ASN+country+pop 对的数据。如果我们没有 any 指标的数据(如果我们无法计算 rtt
、rexb
、nf
、nae
、or override
) 那么我们就不会为那个 ASN+country+pop 插入一行。但是,如果我们至少有一个指标(也许我们有足够的数据来计算 rtt
但不足以计算 nae
),那么我们用 NULL
填充缺失的列
如果我们简单地将 NULL
列替换为 0
之类的内容,那么我们就有低估平均值的风险
I don't think sum(rtt*requests)/sum(rtt)
is "avg_rtt"
好消息 -- 这是一个错字
Don't use utf8 for country
实际上我最初在创建table时并没有指定字符集(这是MySQL默认分配的,并且在我输入show create table tcm_myisam
时出现在输出中)
我会尝试更改字符集,但我预计性能不会因此发生有意义的变化
Slow Queries
这个
select distinct(time) from tcm_mysiam;
花费了 0.00 秒,因为我的数据是按时间聚类和索引的,所以它能够从元数据 tables 中回答查询,而不是执行 table 扫描
select time, count(*) from tcm_myisam group by time;
如果我的理解是正确的,
也应该能够使用这些元数据table——但事实并非如此
Deleting after 90 days
到目前为止,我只从 1 月初开始收集数据,所以我们还没有完整的 90 天数据(这意味着 "delete" 声明尚未 运行之前的数据库)。为了在达到约 130 亿行后测试性能,我 运行 一个脚本在测试数据库上生成假数据。
我的印象是,通过将 time
作为我的主键(因此按时间聚类),删除会很快。但是,我会考虑将分区作为一个额外的步骤来提高性能。
Summary table
此摘要table 已存在。存在 500k 行的批次,以便我们可以深入了解这些摘要的计算方式。
例如,如果摘要 table 显示:"India saw a spike in RTT at 5pm three days ago",我们可以深入研究三天前下午 5 点印度的所有数据,以确定哪些 ASN 或 POP 受到了影响。
附录: 我目前有两个摘要 table。 returns 每个国家/地区所有指标的最小值、最大值和加权平均值(汇总所有 ASN 和 POP 值)。 returns 每个 ASN 的所有指标的最小值、最大值和加权平均值(汇总所有国家和 POP 值)。这些摘要 table 有效地缩减了我的密钥:
(time, asn, country, pop) -> (time, country)
(time, asn, country, pop) -> (time, asn)
我不会将 "count of rows" 添加到这些摘要 table 中。因此,通过添加我可以使用摘要 table 比使用原始 table.
更快地获得每次的总计数
此外,我没有 table 给定时间的 return 有意义数据的摘要:
(time, asn, country, pop) -> (time)
这样的table不仅可以包括"count of rows",还可以包括"number of rows which exceeded a certain threshold"或"number of distinct ASNs"。所以我将添加这样一个 table 并调整我的应用程序以在适当的时候从中读取。
Absurdly slow
我很清楚阅读全部 130 亿行 需要时间。即使在连接到专用 PCI-e 3.0x4 线路(大约 32 GB/s 带宽)的 M.2 SSD 上,我们也需要 5-8 秒才能从磁盘读取主键....那就是如果我们正在读取所有 130 亿行
我的索引目标是避免一次读取所有 130 亿行。所有 130 亿行都必须可用(我们是否应该选择读取它们),但我们一次最多只能读取 500,000 行(当我们在给定时间内请求 "all data" 时)。因此,我们不是读取 130 亿个主键,而是读取 26000 个 "time" 键来筛选出我们真正想要的 500,000 行,然后读取这 500,000 行。总共从磁盘(索引+数据)读取了 526,000 行,磁盘减少了 5-6 个数量级 I/O.
在大多数情况下,这很有效。我当然没有专用 PCI-e 3.0x4 线路上的 M.2 SSD。我在共享 SATA 线路上有一个糟糕的盘片磁盘,它正在被同一台机器上的其他应用程序 运行ning 同时写入和读取。我很幸运看到 50 MB/s 读取速度。尽管如此,我还是看到查询在 1 分钟内完成(通常)。
然而 select time, count(*)
查询让我感到困惑,因为我认为这会利用我的索引,而是它扫描了整个 table(导致 25 分钟我的破磁盘的执行时间)
所以我原来的问题的关键是:
如何在使用 group by
时获取 count(*)
查询以利用索引提高性能?
请注意,更简单的查询 select count(*) from tcm_myisam
使用 table 元数据和 return 几乎是即时的。
架构和查询更改
使用 InnoDB,而不是 MyISAM。这将导致磁盘占用空间显着增加;下面,我建议摆脱二级索引,这将减轻打击。不过,足迹可能是原来的两倍。
编辑:InnoDB的原因:(1)崩溃安全,(2)PK效率。 InnoDB虽然有"more overhead",但近十年来所有的性能提升都是针对InnoDB的。因此,尽管 "overhead",InnoDB 通常还是一样快或更快。我想知道在添加我的索引建议后 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
编辑:"eliminate this index which exactly matches the columns" -- 因为PK是索引,所以我没有去掉索引。此外,由于 PK 与数据 "clustered",this 查询在 InnoDB 中本质上 运行 比 MyISAM 更快。 (MyISAM 必须在 PK BTree 和数据之间来回反弹;InnoDB 不需要。)
编辑:我从 GROUP BY
中去掉了 asn
和 country
,这样 GROUP BY
和 ORDER BY
可以相同,从而避免一种额外的种类。 (它与 WHERE
无关,只是注意到这两列是用 =
测试的,因此与 GROUP BY
无关。)
编辑:"The queries I posted here are not the only queries being performed on the data." -- 好吧,在我也看到他们之前,我无法完成对您的帮助。我已经为所提供的查询提供了建议。我的建议可能会或可能不会 帮助或伤害 其他查询。
编辑 "it makes sense to have time be the first column in my primary index for clustering reasons" -- 是和否。'Yes',如果主要 activity 是 INSERTing
。 'No' 如果主要 activity 是 SELECTing
and/or 如果集群提供了显着的性能提升。
现在 25920 rows in set, 4012 warnings (15.55 sec)
将 运行 显着加快。但是您还应该使用
检查警告
SHOW WARNINGS LIMIT 20;
您是故意使用 NULL
吗?或者列可以是 NOT NULL
?算术会不会乱了?
我不认为 sum(rtt*requests)/sum(rtt)
是 "avg_rtt"。也许除以 sum(requests)
??
不要为 country
使用 utf8;也许也不适合 pop
?
编辑:在某些 versions/engines 中,这将占用 6 个字节。更大 table --> 较慢的查询(有点)。
慢查询
这个
select distinct(time) from tcm_myisam;
花了 0.00 秒,要么是因为 MyISAM,要么是因为您打开了查询缓存。它可能应该被关闭,因为现金由于插入而每 5 分钟清除一次。
编辑:我很好奇。你能提供EXPLAIN select ...
吗?还要用 select SQL_NO_CACHE ...
计时以避免 QC。可能对 SELECT DISTINCT
进行了优化,跳过了索引。
select time, count(*) from tcm_myisam group by time;
需要 table 扫描,所以它注定会很慢,并且随着 table 的增长而变慢。稍后我会提出解决方案。
90 天后删除
你测试过这个吗?你见过它有多贵吗?让我们用 PARTITIONing
来解决这个问题。我建议 PARTITION BY RANGE(TO_DAYS(time))
。那将需要大约 16 个分区。您每周 DROP PARTITION
一次,每周 REORGANIZE
一次。详情在这里:http://mysql.rjweb.org/doc.php/partitionmaint
这将使 "delete" 瞬间发生。它会减慢一些原始查询的速度,但我认为这种权衡是值得的。速度变慢的原因是必须从 16 个分区中的每个分区中获取一些行。
编辑:"the deletes would be fast [if time
is first]" -- 它变得比这更复杂。在 MyISAM 中,会在数据中刻出一个巨大的洞。这个洞将由随后的 INSERTs
填补,直到下一个 "delete"。随着时间的推移,MyISAM table 将变得越来越碎片化。使用 InnoDB,也会有 "hole",但基本上没有 "fragmentation"。在这两种情况下 table 都不会缩小;会有免费的 space。是的,如果 PK starts with time
,删除会比我建议的 PK 快一些。然而 DROP PARTITION
将比 DELETE
.
快得多
编辑:"should also be able to use these metadata tables" -- 唯一接近 "metadata" 的是 MyISAM 保持 总 行计数。这对于既没有 WHERE
也没有 GROUP BY
的 COUNT(*)
来说肯定更好。但是只针对那个查询。
编辑:"we read 26000 "时间“过滤我们实际需要的 500,000 行的键”——注意 PARTITION BY (TO_DAYS(time))
允许粗略的 WHERE time BETWEEN .. AND ..
另外到 WHERE
中的任何其他内容(例如 asn
)。也就是说,分区给出了二维索引的粗略近似。所以...即使我从PK开始就移动了time
,你仍然不需要读取130亿行来获得一个很短的时间范围。任何过滤到一周以下的查询都只会命中 1 或 2 个分区(取决于时间范围与分区的对齐方式),因此只有 1 或 20 亿行,而不是 13。
总结Table
通常,在像这样的数据仓库情况下,构建和维护 "Summary Table" 可以显着提高性能(可能是 10 倍)。
在你的情况下,代替(或除了)投掷500K raw 行到 Fact table 中,总结它们并将它们放入另一个 table。然后对 table.
执行 SELECTs
不明白为什么每批有 500K 行,我不能更具体。
关于摘要 table 的一些通用信息:http://mysql.rjweb.org/doc.php/summarytables
编辑:"aggregates several metrics across all time" -- 摘要 tables.
的主要原因
慢得离谱
13 billlion 行(PK 需要 200GB?)read 需要时间。它将是 I/O-bound。我的更改将使该查询 运行 变慢;但这是一个重要的问题吗?一个 suitable 摘要 table 可以更快地获得计数。
以下是我的show create table
我的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
table是一个日志。每 5 分钟我 运行 一个脚本来向这个 table 添加大约 500,000 行,每行由 (time, asn, pop, country)
唯一键控。对于给定的 asn, pop, country
三元组,每次脚本 运行 时我都会计算几个指标,然后将这些指标转储到 table。以这种方式附加到 table 之后,这些行永远不会被修改——尽管我确实删除了超过 90 天的数据。
在整整 90 天后,我们收集了大约每 5 分钟 500,000 行:
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 修改了他的 post 以回应此。有关详细信息,请参阅他 post 的 "Edit:" 部分。
由于我想 post 作为回应的数量很大,所以我无法将其放入评论中。因此,我针对您在回答中提出的每一点修改了我的 post。
Use InnoDB, not MyISAM
我实际上有两个独立的 tables,因为我正在执行性能实验 -- tcm_myisam
和 tcm_innodb
。
也就是说,考虑 MyISAM 的决定并不是轻率的。 InnoDB 提供了 很多 的功能,超越了 MyISAM,none 我需要:
- 参照完整性 -- 我的 table 中没有外键
- T运行sactions / atomicity -- 我不使用 t运行sactions,写入失败期间损坏的数据不会对我产生负面影响用例
- 行锁定 -- 只有一个脚本写入 table,该脚本永远不会同时 运行ning 超过一次,并且它只会追加或删除行(从不修改它们)。因此我没有从行锁定中受益
- Rollbacks -- 由于我不使用 t运行saction,所以我不使用此功能
因为 MyISAM tables 提供更小的磁盘占用空间(从磁盘读取的数据更少)并提供更简单的 t运行saction 模型,查询开销减少。一般建议是 "if you perform a lot of reads, MyISAM may be faster. If you perform a lot of writes, InnoDB is always faster"。我碰巧属于 MyISAM 优于 InnoDB 的少数用例之一。
在我的测试中,"rather complex" 查询针对给定的 ASN 和国家 运行 聚合了所有时间的多个指标,在 MyISAM 上大约需要 15 秒,在 InnoDB 上大约需要 20 秒。
[Get] rid of the secondary index
建议这样做的唯一原因是 "soften the blow" InnoDB 的更大 table 大小。一般来说,如果您根据列进行分组或选择,最好在其上建立索引。告诉我删除与我分组所依据的列完全匹配的索引是愚蠢的。
Change this [query] to this [query]
我(显然是错误的)相信为了出现在 where
子句中,列必须是 group by
子句的一部分。但是,这两个查询的执行时间相同。您的版本只更简洁几个字符 - 性能增益为零
And change the indexes to [this order]
我在此处 post 进行的查询并不是对数据执行的唯一查询。最常见的数据查询 运行 是 return 给定时间的所有数据 - 因此出于集群原因,将 time
作为我的主索引中的第一列是有意义的。我还同时附加给定 time
的所有数据,并执行定期数据库维护以 p运行e 所有早于某个 time
的数据。由于我对数据库所做的唯一写入是按时间聚类的,因此以任何其他方式对数据进行聚类是没有意义的。
事实上,我在此处 post 编辑的 "absurdly slow" 查询是从这种选择给定时间的所有数据的常见用例中诞生的。我需要估计这些基于时间的组的文件大小,所以我要计算每次有多少行。
通过将我的主键更改为 (asn, country, time, pop)
它可能会适度提高我 post 编辑的 "rather complex" 查询的性能,但它会破坏我的大多数其他查询的性能
Are you deliberately using
NULL
?
在收集指标时,某些指标可能不可用。要么是因为我的一个数据源无法 return 数据,要么是因为我们目前没有特定 ASN+country+pop 对的数据。如果我们没有 any 指标的数据(如果我们无法计算 rtt
、rexb
、nf
、nae
、or override
) 那么我们就不会为那个 ASN+country+pop 插入一行。但是,如果我们至少有一个指标(也许我们有足够的数据来计算 rtt
但不足以计算 nae
),那么我们用 NULL
如果我们简单地将 NULL
列替换为 0
之类的内容,那么我们就有低估平均值的风险
I don't think
sum(rtt*requests)/sum(rtt)
is "avg_rtt"
好消息 -- 这是一个错字
Don't use utf8 for
country
实际上我最初在创建table时并没有指定字符集(这是MySQL默认分配的,并且在我输入show create table tcm_myisam
时出现在输出中)
我会尝试更改字符集,但我预计性能不会因此发生有意义的变化
Slow Queries
这个
select distinct(time) from tcm_mysiam;
花费了 0.00 秒,因为我的数据是按时间聚类和索引的,所以它能够从元数据 tables 中回答查询,而不是执行 table 扫描
select time, count(*) from tcm_myisam group by time;
如果我的理解是正确的,
也应该能够使用这些元数据table——但事实并非如此
Deleting after 90 days
到目前为止,我只从 1 月初开始收集数据,所以我们还没有完整的 90 天数据(这意味着 "delete" 声明尚未 运行之前的数据库)。为了在达到约 130 亿行后测试性能,我 运行 一个脚本在测试数据库上生成假数据。
我的印象是,通过将 time
作为我的主键(因此按时间聚类),删除会很快。但是,我会考虑将分区作为一个额外的步骤来提高性能。
Summary table
此摘要table 已存在。存在 500k 行的批次,以便我们可以深入了解这些摘要的计算方式。
例如,如果摘要 table 显示:"India saw a spike in RTT at 5pm three days ago",我们可以深入研究三天前下午 5 点印度的所有数据,以确定哪些 ASN 或 POP 受到了影响。
附录: 我目前有两个摘要 table。 returns 每个国家/地区所有指标的最小值、最大值和加权平均值(汇总所有 ASN 和 POP 值)。 returns 每个 ASN 的所有指标的最小值、最大值和加权平均值(汇总所有国家和 POP 值)。这些摘要 table 有效地缩减了我的密钥:
(time, asn, country, pop) -> (time, country)
(time, asn, country, pop) -> (time, asn)
我不会将 "count of rows" 添加到这些摘要 table 中。因此,通过添加我可以使用摘要 table 比使用原始 table.
更快地获得每次的总计数此外,我没有 table 给定时间的 return 有意义数据的摘要:
(time, asn, country, pop) -> (time)
这样的table不仅可以包括"count of rows",还可以包括"number of rows which exceeded a certain threshold"或"number of distinct ASNs"。所以我将添加这样一个 table 并调整我的应用程序以在适当的时候从中读取。
Absurdly slow
我很清楚阅读全部 130 亿行 需要时间。即使在连接到专用 PCI-e 3.0x4 线路(大约 32 GB/s 带宽)的 M.2 SSD 上,我们也需要 5-8 秒才能从磁盘读取主键....那就是如果我们正在读取所有 130 亿行
我的索引目标是避免一次读取所有 130 亿行。所有 130 亿行都必须可用(我们是否应该选择读取它们),但我们一次最多只能读取 500,000 行(当我们在给定时间内请求 "all data" 时)。因此,我们不是读取 130 亿个主键,而是读取 26000 个 "time" 键来筛选出我们真正想要的 500,000 行,然后读取这 500,000 行。总共从磁盘(索引+数据)读取了 526,000 行,磁盘减少了 5-6 个数量级 I/O.
在大多数情况下,这很有效。我当然没有专用 PCI-e 3.0x4 线路上的 M.2 SSD。我在共享 SATA 线路上有一个糟糕的盘片磁盘,它正在被同一台机器上的其他应用程序 运行ning 同时写入和读取。我很幸运看到 50 MB/s 读取速度。尽管如此,我还是看到查询在 1 分钟内完成(通常)。
然而 select time, count(*)
查询让我感到困惑,因为我认为这会利用我的索引,而是它扫描了整个 table(导致 25 分钟我的破磁盘的执行时间)
所以我原来的问题的关键是:
如何在使用 group by
时获取 count(*)
查询以利用索引提高性能?
请注意,更简单的查询 select count(*) from tcm_myisam
使用 table 元数据和 return 几乎是即时的。
架构和查询更改
使用 InnoDB,而不是 MyISAM。这将导致磁盘占用空间显着增加;下面,我建议摆脱二级索引,这将减轻打击。不过,足迹可能是原来的两倍。
编辑:InnoDB的原因:(1)崩溃安全,(2)PK效率。 InnoDB虽然有"more overhead",但近十年来所有的性能提升都是针对InnoDB的。因此,尽管 "overhead",InnoDB 通常还是一样快或更快。我想知道在添加我的索引建议后 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
编辑:"eliminate this index which exactly matches the columns" -- 因为PK是索引,所以我没有去掉索引。此外,由于 PK 与数据 "clustered",this 查询在 InnoDB 中本质上 运行 比 MyISAM 更快。 (MyISAM 必须在 PK BTree 和数据之间来回反弹;InnoDB 不需要。)
编辑:我从 GROUP BY
中去掉了 asn
和 country
,这样 GROUP BY
和 ORDER BY
可以相同,从而避免一种额外的种类。 (它与 WHERE
无关,只是注意到这两列是用 =
测试的,因此与 GROUP BY
无关。)
编辑:"The queries I posted here are not the only queries being performed on the data." -- 好吧,在我也看到他们之前,我无法完成对您的帮助。我已经为所提供的查询提供了建议。我的建议可能会或可能不会 帮助或伤害 其他查询。
编辑 "it makes sense to have time be the first column in my primary index for clustering reasons" -- 是和否。'Yes',如果主要 activity 是 INSERTing
。 'No' 如果主要 activity 是 SELECTing
and/or 如果集群提供了显着的性能提升。
现在 25920 rows in set, 4012 warnings (15.55 sec)
将 运行 显着加快。但是您还应该使用
SHOW WARNINGS LIMIT 20;
您是故意使用 NULL
吗?或者列可以是 NOT NULL
?算术会不会乱了?
我不认为 sum(rtt*requests)/sum(rtt)
是 "avg_rtt"。也许除以 sum(requests)
??
不要为 country
使用 utf8;也许也不适合 pop
?
编辑:在某些 versions/engines 中,这将占用 6 个字节。更大 table --> 较慢的查询(有点)。
慢查询
这个
select distinct(time) from tcm_myisam;
花了 0.00 秒,要么是因为 MyISAM,要么是因为您打开了查询缓存。它可能应该被关闭,因为现金由于插入而每 5 分钟清除一次。
编辑:我很好奇。你能提供EXPLAIN select ...
吗?还要用 select SQL_NO_CACHE ...
计时以避免 QC。可能对 SELECT DISTINCT
进行了优化,跳过了索引。
select time, count(*) from tcm_myisam group by time;
需要 table 扫描,所以它注定会很慢,并且随着 table 的增长而变慢。稍后我会提出解决方案。
90 天后删除
你测试过这个吗?你见过它有多贵吗?让我们用 PARTITIONing
来解决这个问题。我建议 PARTITION BY RANGE(TO_DAYS(time))
。那将需要大约 16 个分区。您每周 DROP PARTITION
一次,每周 REORGANIZE
一次。详情在这里:http://mysql.rjweb.org/doc.php/partitionmaint
这将使 "delete" 瞬间发生。它会减慢一些原始查询的速度,但我认为这种权衡是值得的。速度变慢的原因是必须从 16 个分区中的每个分区中获取一些行。
编辑:"the deletes would be fast [if time
is first]" -- 它变得比这更复杂。在 MyISAM 中,会在数据中刻出一个巨大的洞。这个洞将由随后的 INSERTs
填补,直到下一个 "delete"。随着时间的推移,MyISAM table 将变得越来越碎片化。使用 InnoDB,也会有 "hole",但基本上没有 "fragmentation"。在这两种情况下 table 都不会缩小;会有免费的 space。是的,如果 PK starts with time
,删除会比我建议的 PK 快一些。然而 DROP PARTITION
将比 DELETE
.
编辑:"should also be able to use these metadata tables" -- 唯一接近 "metadata" 的是 MyISAM 保持 总 行计数。这对于既没有 WHERE
也没有 GROUP BY
的 COUNT(*)
来说肯定更好。但是只针对那个查询。
编辑:"we read 26000 "时间“过滤我们实际需要的 500,000 行的键”——注意 PARTITION BY (TO_DAYS(time))
允许粗略的 WHERE time BETWEEN .. AND ..
另外到 WHERE
中的任何其他内容(例如 asn
)。也就是说,分区给出了二维索引的粗略近似。所以...即使我从PK开始就移动了time
,你仍然不需要读取130亿行来获得一个很短的时间范围。任何过滤到一周以下的查询都只会命中 1 或 2 个分区(取决于时间范围与分区的对齐方式),因此只有 1 或 20 亿行,而不是 13。
总结Table
通常,在像这样的数据仓库情况下,构建和维护 "Summary Table" 可以显着提高性能(可能是 10 倍)。
在你的情况下,代替(或除了)投掷500K raw 行到 Fact table 中,总结它们并将它们放入另一个 table。然后对 table.
执行SELECTs
不明白为什么每批有 500K 行,我不能更具体。
关于摘要 table 的一些通用信息:http://mysql.rjweb.org/doc.php/summarytables
编辑:"aggregates several metrics across all time" -- 摘要 tables.
的主要原因慢得离谱
13 billlion 行(PK 需要 200GB?)read 需要时间。它将是 I/O-bound。我的更改将使该查询 运行 变慢;但这是一个重要的问题吗?一个 suitable 摘要 table 可以更快地获得计数。