【问题标题】:Are full count queries really so slow on a large MySQL InnoDB tables?大型 MySQL InnoDB 表上的全计数查询真的这么慢吗?
【发布时间】:2020-01-15 04:45:48
【问题描述】:

我们有一个包含数百万条目的大表。完整计数非常慢,请参见下面的代码。这对于 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)

【问题讨论】:

标签: mysql performance innodb mysql-slow-query-log


【解决方案1】:

除了比尔所说的......

最小索引

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 entries”,好像 2 个有效数字“足够好”。如果您在 UI 上向用户显示计数,那不是“足够好”吗?如果是这样,性能的一种解决方案是每天进行一次计数并将其存储在某个地方。这将允许即时访问“足够好”的值。

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

向它扔硬件

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

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

【讨论】:

  • 哇,比尔和瑞克,非常感谢这个详细的回答!我现在明白了这个问题。我会选择两个帖子作为“答案”,但不幸的是我只能选择一个;)
【解决方案2】:

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

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

但是,鉴于在您进行计数时许多行正在被添加或删除,所以表中的行数是多少?答案很模糊。

您的事务不应“看到”在事务开始后创建的行版本。同样,即使其他人请求删除它们,您的事务也应该计算行数,但他们是在您的事务开始后才这样做的。

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

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

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

【讨论】:

  • 哇,比尔和瑞克,非常感谢这个详细的回答!我现在明白了这个问题。我会选择两个帖子作为“答案”,但不幸的是我只能选择一个;)
猜你喜欢
  • 2010-11-28
  • 2016-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多