繁体   English   中英

在大型MySQL InnoDB表上,全计数查询真的那么慢吗?

[英]Are full count queries really so slow on a large MySQL InnoDB tables?

我们有一个包含数百万个条目的大型表。 完整计数非常慢,请参见下面的代码。 这对于MySQL InnoDB表是否很常见? 有没有办法加速呢? 即使使用查询缓存,它仍然“缓慢”。 我还想知道,为什么具有2.8 mio条目的“通讯”表的计数比具有4.5 mio条目的“事务”的计数慢。

我知道使用where子句可以更快。 我只想知道不良的表现是否正常。

我们正在使用具有m4.xlarge(4 CPU,16 GB RAM,500 GB存储)的Amazon RDS MySQL 5.7。 我也已经尝试了具有更多CPU和RAM的大型实例,但是查询时间没有太大变化。

mysql> SELECT COUNT(*) FROM transaction;
+----------+
| COUNT(*) |
+----------+
|  4569880 |
+----------+
1 row in set (1 min 37.88 sec)

mysql> SELECT COUNT(*) FROM transaction;
+----------+
| count(*) |
+----------+
|  4569880 |
+----------+
1 row in set (1.44 sec)

mysql> SELECT COUNT(*) FROM communication;
+----------+
| count(*) |
+----------+
|  2821486 |
+----------+
1 row in set (2 min 19.28 sec)

这是使用支持多版本并发控制(MVCC)的数据库存储引擎的缺点。

InnoDB允许您将查询隔离在一个事务中,而不会阻止正在读取和写入数据行的其他并发客户端。 这些并发更新不会影响您的事务处理的数据视图。

但是,考虑到在进行计数时有许多行正在添加或删除中,表中的行数是多少? 答案是模糊的。

您的事务不应能够“查看”在事务开始后创建的行版本。 同样,即使其他人已要求删除行,您的事务也应该对行进行计数,但是行在事务开始后才被删除。

答案是,当您执行SELECT COUNT(*)或需要检查许多行的任何其他类型的查询时,InnoDB必须访问每一行,以查看对您的事务视图可见的该行的当前版本。数据库,并对其进行计数(如果可见)。

在不支持事务或并发更新的表(例如MyISAM)中,存储引擎会将总行数保留为表的元数据。 该存储引擎无法支持多个线程同时更新行,因此行的总数不那么模糊。 因此,当您从MyISAM表中请求SELECT COUNT(*) ,它仅返回其在内存中的行数(但是,如果您使用WHERE子句执行SELECT COUNT(*)来计算行的某些子集,这将无用在某种情况下,因此在这种情况下必须对其进行计数)。

通常,大多数人发现InnoDB对并发更新的支持非常有价值,并且他们愿意牺牲SELECT COUNT(*)的优化。

除了比尔说的...

最小指数

InnoDB选择“最小”索引来执行COUNT(*) 可能所有的communication指标都大于最小的transaction ,因此存在时间差。 在判断索引的大小时,请在PRIMARY KEY列中包含任何辅助索引:

PRIMARY KEY(id),   -- INT (4 bytes)
INDEX(flag),       -- TINYINT (1 byte)
INDEX(name),       -- VARCHAR(255) (? bytes)

对于度量大小, PRIMARY KEY很大,因为它包含(由于群集)表的所有列。 INDEX(flag)为“ 5个字节”。 INDEX(name)可能平均几十个字节。 SELECT COUNT(*)将清楚地选择INDEX(flag)

显然, transaction的索引很小,但是communication却没有。

TEXT / BLOG列有时存储为“脱记录”。 因此,它们不计入PK指数的大小。

查询缓存

如果“查询缓存”已打开,则查询的第二次运行可能比第一次运行快得多。 但这仅是在此期间表没有更改的情况下。 由于对该表的任何更改都会使该表的所有 QC条目失效,因此QC在生产系统中很少有用。 “更快”是指大约0.001秒; 不是1.44秒。

1m38s和1.44s之间的差异可能是由于buffer_pool中缓存的内容所致,后者是InnoDB的常规缓存区域。 第一次运行可能未在RAM中找到“最小”索引,因此它进行了大量I / O,花费98秒来获取该索引的所有4.5M行。 第二次运行发现所有缓存在buffer_pool中的数据,因此它以CPU速度(无I / O)运行,因此速度更快。

够好了

在这种情况下,我完全质疑执行COUNT(*)的必要性。 注意您怎么说“ 2.8 mio entry”,好像2位有效数字“足够好”一样。 如果要在UI上向用户显示计数,那还不够“好”吗? 如果这样的话,一种解决方案是每天进行一次计数并将其存储在某个位置。 这将允许瞬时访问“足够好”的值。

还有其他技术。 一种是使用活动代码或某种形式的摘要表来保持计数器的更新。

扔硬件

您已经发现更改硬件没有帮助。

  • 98年代是RDS的任何I / O产品都能运行的最快速度。
  • 1.44的速度与任何一个RDS CPU可以运行的速度一样快。
  • MySQL(及其变体)每个查询使用的CPU不超过一个。
  • 您有足够的RAM,因此整个“小”索引将一直容纳在buffer_pool中,直到您的第二个SELECT COUNT(*).. (RAM太少会导致第二个运行非常慢。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM