在大型 MySQL InnoDB 表上,完整计数查询真的这么慢吗?

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

我们有一个很大的 tables,有数百万个条目。完整计数非常慢,请参见下面的代码。这对于 MySQL InnoDB table 来说很常见吗?有没有办法加速这个? 即使有查询缓存,它仍然是 "slow"。 我也想知道,为什么 "communication" table 上有 2.8 个 mio 条目的计数比 "transaction" 上有 4.5 个 mio 条目的计数慢。

我知道使用 where 子句要快得多。我只想知道性能不好是否正常

我们使用 Amazon RDS MySQL 5.7 和 m4.xlarge(4 CPU,16 GB RAM,500 GB 存储空间)。我也已经尝试过具有更多 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)

这是使用支持multi-versioning concurrency control (MVCC)的数据库存储引擎的缺点。

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

但是 table 中的行数是多少,因为在您进行计数时许多行正在添加或删除?答案很模糊。

您的事务不应 "see" 在您的事务开始后创建的行版本。同样,即使其他人要求删除行,您的事务也应该对行进行计数,但他们是在您的事务开始后才这样做的。

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

在不支持事务或并发更新的 table 中,例如 MyISAM,存储引擎将总行数作为 table 的元数据。这个存储引擎不支持多线程同时更新行,所以总行数不太模糊。因此,当您从 MyISAM table 请求 SELECT COUNT(*) 时,它只是 returns 它在内存中的行数(但是如果您使用 SELECT COUNT(*) WHERE 子句根据某些条件计算某些行的子集,因此在这种情况下它必须实际计算它们)。

一般来说,大多数人觉得InnoDB对并发更新的支持很有价值,愿意牺牲SELECT COUNT(*)的优化。

除了比尔所说的...

最小索引

InnoDB 选择 'smallest' 索引来执行 COUNT(*)。可能 communication 的所有索引都大于 transaction 的最小索引,因此存在时间差异。判断索引大小时,将 PRIMARY KEY 列包含在任何二级索引中:

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

对于测量大小,PRIMARY KEY 很大,因为它包括(由于聚类)table 的所有列。 INDEX(flag) 是“5 个字节”。 INDEX(name) 大概平均几十个字节。 SELECT COUNT(*) 显然会选择 INDEX(flag).

显然 transaction 有一个 'small' 索引,但 communication 没有。

TEXT/BLOG 列有时存储 "off-record"。因此,它们不计入PK指数的大小。

查询缓存

如果 "Query cache" 开启,查询的第二个 运行 宁 可能 比第一个快得多。但这只是在 table 同时没有变化的情况下。由于 any 对 table 的更改会使 all QC 条目失效 table,QC 在生产系统中很少有用. "faster" 我的意思是大约 0.001 秒;不是 1.44 秒。

1m38s 和 1.44s 之间的差异可能是由于缓存在 buffer_pool 中的内容——InnoDB 的一般缓存区域。第一个 运行 可能在 RAM 中找到了 'smallest' 索引的 none,所以它做了很多 I/O,用了 98 秒来获取该索引的所有 4.5M 行。第二个 运行 找到了缓存在 buffer_pool 中的所有数据,因此它 运行 以 CPU 的速度(没有 I/O),因此速度更快。

足够好

在这种情况下,我完全怀疑做 COUNT(*) 的必要性。注意你是怎么说“2.8 mio 条目”的,就好像 2 位有效数字是 "good enough"。如果您在 UI 上向用户显示计数,那不是 "good enough" 吗?如果是这样,性能的一种解决方案是每天计数一次并将其存储在某个地方。这将允许即时访问 "good enough" 值。

还有其他技巧。一种是使用活动代码或某种形式的摘要 Table.

保持计数器更新

向它扔硬件

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

  • 98 年代的速度与 RDS 的任何 I/O 产品一样快 运行。
  • 1.44 秒的速度与任何一个 RDS CPU 一样快 运行。
  • MySQL(及其变体)每个查询不使用超过一个 CPU。
  • 您有足够的 RAM,因此整个 'small' 索引将适合 buffer_pool 直到您的第二个 SELECT COUNT(*)..(太少的 RAM 会导致第二个 运行很慢。)