为什么 count(*) 查询在某些表上很慢,但在其他表上却没有?
Why is count(*) query slow on some tables but not on others?
我在 wamp 服务器上有一个 mysql 数据库 运行,我用它来对 Flickr 数据进行频繁模式挖掘。在将数据加载到数据库的过程中,我 运行 一个计数查询以确定我已经加载了多少图像。我很惊讶
花了 3 分 49 秒
select count(*) from image;
在单独的 table、"concept" 中,我存储了用户为其图像提供的标签列表。 "concept" table 上的类似查询耗时 0.8 秒。神秘之处在于两个 table 都有大约 200,000 行。 select count(*) from image;
returns 283,890 和 select count(*) from concept;
returns 213,357。
这是每个 table
的描述
显然 "image" table 有更大的行。我认为也许 "image" 太大而无法基于 this blog post, so I also tested the size of the tables using code from this answer.
保存在内存中
SELECT table_name AS "Tables",
round(((data_length + index_length) / 1024 / 1024), 2) "Size in MB"
FROM information_schema.TABLES
WHERE table_schema = "$DB_NAME"
ORDER BY (data_length + index_length) DESC;
"image" 为 179.98 MB,"concept" 为 15.45 MB
我 运行 mysql 使用 64 GB RAM 的机器,所以这两个 table 应该很容易安装。我错过了什么减慢了我的查询速度?我该如何解决?
在 InnDB table 上执行 SELECT COUNT(*)
时,MySQL 必须扫描索引以计算行数。在这种情况下,您唯一的索引是主(聚集)索引,因此 MySQL 扫描它。
对于聚集索引,实际的 table 数据也存储在那里。不包括开销,您的 image
table 每行大约 1973 个字节(我假设两个主键列的单字节字符集)。每 (16k) 页最多约有 8 条记录,因此约有 35,486 页。您的 comcept
table 每行大约 257 个字节。每页大约有 63 条记录,因此大约有 3,386 页。这是必须扫描的数据量的巨大差异。
它必须完整地阅读每一页,因为页面可能不完整。
那么,就性能而言,也许这些页面中的一些在内存中而一些不在。由于 MySQL 的 15/16 偏好,也存在一些边际差异,但以上所有数字均应视为近似值。
解决方案
向较大的 table 添加二级索引应该会为 SELECT COUNT(*)
产生与较小的 table 大致相同的性能。当然,要更新另一个索引,更新会慢一点。
为了提高性能,缩短您的主键,因为二级索引包括索引列和完整的主键。
如果您只需要估计的行数,您可以使用以下之一的 rows
值,它使用 table 统计信息而不是扫描索引:
SHOW TABLE STATUS LIKE 'image'
或
EXPLAIN SELECT COUNT(*) FROM image
如果您要查找的是大概数字而不是精确计数,那么 show table status
中的“行”列可能就足够了。它对 InnoDB 表并不总是准确的,但看起来你可能对粗略估计没问题。
我在 wamp 服务器上有一个 mysql 数据库 运行,我用它来对 Flickr 数据进行频繁模式挖掘。在将数据加载到数据库的过程中,我 运行 一个计数查询以确定我已经加载了多少图像。我很惊讶
花了 3 分 49 秒select count(*) from image;
在单独的 table、"concept" 中,我存储了用户为其图像提供的标签列表。 "concept" table 上的类似查询耗时 0.8 秒。神秘之处在于两个 table 都有大约 200,000 行。 select count(*) from image;
returns 283,890 和 select count(*) from concept;
returns 213,357。
这是每个 table
的描述显然 "image" table 有更大的行。我认为也许 "image" 太大而无法基于 this blog post, so I also tested the size of the tables using code from this answer.
保存在内存中SELECT table_name AS "Tables",
round(((data_length + index_length) / 1024 / 1024), 2) "Size in MB"
FROM information_schema.TABLES
WHERE table_schema = "$DB_NAME"
ORDER BY (data_length + index_length) DESC;
"image" 为 179.98 MB,"concept" 为 15.45 MB
我 运行 mysql 使用 64 GB RAM 的机器,所以这两个 table 应该很容易安装。我错过了什么减慢了我的查询速度?我该如何解决?
在 InnDB table 上执行 SELECT COUNT(*)
时,MySQL 必须扫描索引以计算行数。在这种情况下,您唯一的索引是主(聚集)索引,因此 MySQL 扫描它。
对于聚集索引,实际的 table 数据也存储在那里。不包括开销,您的 image
table 每行大约 1973 个字节(我假设两个主键列的单字节字符集)。每 (16k) 页最多约有 8 条记录,因此约有 35,486 页。您的 comcept
table 每行大约 257 个字节。每页大约有 63 条记录,因此大约有 3,386 页。这是必须扫描的数据量的巨大差异。
它必须完整地阅读每一页,因为页面可能不完整。
那么,就性能而言,也许这些页面中的一些在内存中而一些不在。由于 MySQL 的 15/16 偏好,也存在一些边际差异,但以上所有数字均应视为近似值。
解决方案
向较大的 table 添加二级索引应该会为 SELECT COUNT(*)
产生与较小的 table 大致相同的性能。当然,要更新另一个索引,更新会慢一点。
为了提高性能,缩短您的主键,因为二级索引包括索引列和完整的主键。
如果您只需要估计的行数,您可以使用以下之一的 rows
值,它使用 table 统计信息而不是扫描索引:
SHOW TABLE STATUS LIKE 'image'
或
EXPLAIN SELECT COUNT(*) FROM image
如果您要查找的是大概数字而不是精确计数,那么 show table status
中的“行”列可能就足够了。它对 InnoDB 表并不总是准确的,但看起来你可能对粗略估计没问题。