20

我有一个页面,其中有 4 个选项卡,显示基于不同表格的 4 个不同报告。

我使用select count(*) from <table>查询获取每个表的行数,并在选项卡上显示每个表中可用的行数。结果,每个页面回发导致count(*)执行 5 个查询(4 个用于获取计数,1 个用于分页)和 1 个用于获取报告内容的查询。

现在我的问题是:count(*)查询真的很贵吗——我应该将行数(至少是那些显示在选项卡上的)保持在页面的视图状态而不是多次查询吗?

COUNT(*) 查询的成本是多少?

4

7 回答 7

11

一般来说,成本COUNT(*)成本与满足查询条件的记录数加上准备这些记录所需的时间(这取决于底层查询的复杂性)成正比。

在处理单个表的简单情况下,通常会进行特定的优化以使此类操作变得便宜。例如,COUNT(*)没有WHERE条件从单个MyISAM表中执行MySQL- 这是即时的,因为它存储在元数据中。

例如,让我们考虑两个查询:

SELECT  COUNT(*)
FROM    largeTableA a

由于每条记录都满足查询,因此COUNT(*)成本与表中的记录数成正比(即,与它返回的内容成正比)(假设它需要访问行并且没有特定的优化来处理它)

SELECT  COUNT(*)
FROM    largeTableA a
JOIN    largeTableB b
ON      a.id = b.id

在这种情况下,引擎很可能会使用HASH JOIN,执行计划将是这样的:

  1. 在较小的表上构建哈希表
  2. 扫描较大的表,在哈希表中查找每条记录
  3. 在比赛进行时数数比赛。

在这种情况下,COUNT(*)开销(第 3 步)可以忽略不计,查询时间将完全由第 1 步和第 2 步定义,即构建哈希表并进行查找。对于这样的查询,时间将是O(a + b):它并不真正取决于匹配的数量。

a.id但是,如果和上都有索引b.id,则MERGE JOIN可以选择 并且COUNT(*)时间将再次与匹配次数成正比,因为每次匹配后都会执行索引查找。

于 2010-04-27T10:13:57.543 回答
8

您需要附加SQL ProfilerL2SProf 之类的应用程序级别分析器,然后在您的上下文中查看实际查询成本:

  • 猜测问题是什么并尝试确定潜在解决方案的可能好处

  • 允许其他人在 da 互联网上为您猜测 - 有很多没有引用的错误信息,包括在这个线程中(但不在这篇文章中:P)

完成后,您将清楚最好的方法是什么 - 即 SELECT COUNT 是否占主导地位,等等。

完成此操作后,您还将知道您选择做的任何更改是否产生了积极或消极的影响。

于 2010-04-27T10:46:14.373 回答
3

正如其他人所说COUNT(*),总是对行进行物理计数,所以如果你可以这样做一次并缓存结果,那当然是更可取的。

如果您进行基准测试并确定成本可以忽略不计,那么您(目前)没有问题。

如果结果对您的场景来说太贵了,您可以使用“显示大约 30,000的 1 到 500 个”使您的分页“模糊”

SELECT rows FROM sysindexes WHERE id = OBJECT_ID('sometable') AND indid < 2

这将返回行数的近似值(它的近似值,因为它直到检查点才更新)。

于 2010-04-27T11:05:31.563 回答
1

如果页面变慢,您可以查看的一件事是尽可能减少数据库往返次数。即使你的COUNT(*)查询是 O(1),如果你做的足够多,那肯定会减慢速度。

不是一次设置和执行 5 个单独的查询,而是SELECT在一个批处理中运行语句并一次处理 5 个结果。

即,如果您使用的是 ADO.NET,请执行以下操作(为简洁起见省略了错误检查;为清楚起见,非循环/非动态):

string sql = "SELECT COUNT(*) FROM Table1; SELECT COUNT(*) FROM Table2;"

SqlCommand cmd = new SqlCommand(sql, connection);
SqlDataReader dr = cmd.ExecuteReader();

// Defaults to first result set
dr.Read();
int table1Count = (int)dr[0];

// Move to second result set
dr.NextResult();
dr.Read();
int table2Count = (int)dr[0];

如果您使用某种 ORM,例如 NHibernate,则应该有一种方法可以启用自动查询批处理。

于 2010-04-27T12:52:49.663 回答
0

COUNT(*) 可能特别昂贵,因为它可能会导致加载(和分页)整个表,而您可能只需要对主键进行计数(在某些实现中它已被优化)。

从它的声音来看,您每次都会导致表加载操作,这很慢,但除非它运行明显缓慢,或者导致某种问题,否则不要优化:过早和不必要的优化会导致大量麻烦!

对索引主键的计数会快得多,但是由于拥有索引的成本,这可能没有任何好处。

于 2010-04-27T10:36:13.827 回答
0

所有的 I/O 都是昂贵的,如果你可以在没有它的情况下完成任务,你应该这样做。但如果需要,我不会担心。

您提到将计数存储在视图状态中,当然是一种选择,只要当计数错误时代码的行为是可以接受的,因为基础记录已经消失或已添加。

于 2010-04-27T10:56:48.663 回答
0

这取决于您如何处理此表中的数据。如果它们经常更改并且您每次都需要它们,那么也许您可以制作触发器来填充另一个表,该表仅包含该表中的计数。如果您需要单独显示这些数据,也许您可​​以只为一个特定的表执行“select count(*)...”。我立刻想到了这一点,但我敢肯定,还有其他方法可以加快速度。缓存数据,也许?:)

于 2010-04-27T11:57:55.383 回答