在大型 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 会导致第二个 运行很慢。)
我们有一个很大的 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 会导致第二个 运行很慢。)