【问题标题】:Count rows in an SQL table in O(1)在 O(1) 中计算 SQL 表中的行数
【发布时间】:2010-09-27 08:20:12
【问题描述】:

我了解计算 SQL 表中行数的最佳方法是 count(*)(或等效的 count(PrimaryKey))。

  1. 这是 O(1) 吗?
  2. 如果没有,为什么不呢?

为什么不直接实现一个计数器并为这个特定的查询返回它呢?是不是因为这个查询不是一个常见的用例?

如果答案因 SQL 引擎而异,我想听听不同之处 - 但无论如何,我对生产 SQL 引擎中的实际实现感兴趣。

【问题讨论】:

    标签: sql performance count


    【解决方案1】:

    一些 RDBM 中,这是 O(1)(最显着的是 MySQL),在 AFAIK 中,它通常不受欢迎,并被认为是“丑陋的性能黑客”。原因是,如果您有事务(每个真正的 RDBM 都应该有),那么表中的总行数可能等于也可能不等于总行数您可以从当前事务中看到。这就是为什么服务器需要检查哪些行对您的事务实际可见,使其 O(n) 多于 O(1)。

    如果您想优化获取行数的过程并对近似结果感到满意,大多数 RDBM 都有特殊的“信息”表,其中包含有关表的信息,包括大致的行数(同样,它是不是确切的行数,因为事务)。

    【讨论】:

    • 请注意,在 MySQL 中也不是 O(1)。但是,如果您有一个自动递增的 ID 字段,您可以使用一个小技巧并执行“从 table order by id desc limit 1 中选择 id”。
    • 如果使用 MyISAM,MySQL 为 O(1) - 来自手册 dev.mysql.com/doc/refman/5.1/en/…:“COUNT(*) 被优化为如果 SELECT 从一个表中检索,则返回非常快,没有检索到其他列,并且没有 WHERE 子句。” ... “此优化仅适用于 MyISAM 表,因为为此存储引擎存储了准确的行数,并且可以非常快速地访问。”
    【解决方案2】:

    不,这不是一个常见的用例。我见过的大多数行数都涉及到一些 where 子句。

    这未实现的主要原因是行计数器将成为多用户环境中争用的原因。每次插入或删除一行时,计数器都需要更新,有效地为每次插入/删除锁定整个表。

    【讨论】:

      【解决方案3】:

      基于索引或表的 COUNT(*) 的性能实际上取决于段大小。你可以有一个只有一行的 1GB 表,但 Oracle 可能仍然需要扫描整个分配的空间。如果不改变高水位线,再插入一百万行可能根本不会影响性能。索引以类似的方式工作,不同的删除模式可能会在索引结构中留下不同数量的可用空间,并导致索引扫描提供比 O(N) 更好或更差的性能。

      所以,理论上它是 O(N)。在实践中,存在可能导致其非常不同的实施问题。

      例如,在某些情况下,Oracle 数据仓库的性能可能优于 O(N)。特别是优化器可以扫描位图索引,位图索引的大小与表的大小只有微弱的关系,这与 b 树索引不同。这是因为压缩方法使索引大小取决于表的大小、唯一值的数量、整个表中值的分布以及我相信的历史加载模式。因此,将表中的行数增加一倍可能只会使索引大小增加 10%。

      在存在物化视图的情况下,您也可能通过读取汇总表得到 O(1)(触发器是一种不安全的做法)。

      【讨论】:

        【解决方案4】:

        在 MS SQL 服务器中,对表执行 Count(*) 总是会执行索引扫描(在主键上)或表扫描(都不好)。对于大型表,这可能需要一段时间。

        相反,有一个很酷的技巧可以几乎立即显示当前记录数(与 Microsoft 在您右键单击表格并选择属性时使用的相同):

        --SQL 2005 or 2008
        select sum (spart.rows)
        from sys.partitions spart
        where spart.object_id = object_id('YourTable')
        and spart.index_id < 2
        
        --SQL 2000
        select max(ROWS) from sysindexes
        where id = object_id('Resolve_Audit')
        

        根据 SQL 更新索引统计信息的频率,这个数字可能会略有偏差,但如果您需要一个大致的数字,而不是一个确切的数字,那么这些非常有用。

        【讨论】:

          【解决方案5】:

          这不是固定时间,因为在事务引擎中需要检查当前事务中存在多少行,这通常涉及全表扫描。

          不带 where 子句的 COUNT(*) 优化对于数据库来说并不是一个特别有用的优化,它以牺牲其他东西为代价;大表的用户很少做这样的查询,如果存在 WHERE 子句,这将毫无帮助。

          MySQL 中的 MyISAM 通过存储确切的行数来“欺骗”,但它只能这样做,因为它没有 MVCC,因此不需要担心行在哪些事务中。

          【讨论】:

            【解决方案6】:

            对于 Oracle,它通常为 O(N),除非查询结果在缓存中, 因为它本质上必须迭代所有块,或者迭代索引来计算它们。

            【讨论】:

              【解决方案7】:

              通常是 O(N)。

              如果需要对此类查询做出 O(1) 响应,您可以使用以下任一方法轻松完成:

              • 对查询进行计数的索引视图。
              • 在另一个表中手动存储计数并使用触发器更新行计数:

              例子:

              CREATE TABLE CountingTable ( Count int )
              
              INSERT CountingTable VALUES(0)
              
              CREATE TRIGGER Counter ON Table FOR INSERT, UPDATE, DELETE AS
              BEGIN
                 DECLARE @added int, @Removed int
                 SET @added = SELECT COUNT(*) FROM inserted
                 SET @removed = SELECT COUNT(*) FROM deleted
                 UPDATE CountingTable SET Count = Count + @added - @removed
              END
              

              【讨论】:

              • 触发器是一个序列化过程,可能会导致争用或计数不准确
              • 我认为它会在处理大量并发事务时大大降低性能。但算错了,我不认为。至少,如果您的隔离级别 >= READ_COMMITED,它将返回您的事务中的计数
              • 嗯,如果你截断表格呢?
              • 当你想出这样一个丑陋的黑客时,你不应该截断表格。您甚至可以直接修改计数:)。无论如何,我没有认可它,但正常使用它应该不会产生错误的结果。如果您的隔离级别为 READ_UNCOMMITED,您最终可能会遇到竞态条件和错误计数。
              【解决方案8】:

              SQL Server 中有一个(不准确的)快捷方式,您可以在其中查看元数据 sys.partitions 中特定对象(如表上的索引)的计数。

              操作是 O(1),但只是一个估计。

              【讨论】:

                【解决方案9】:

                数据库可以存储表中的行数并响应 O(1) select count(*) From MyTable

                但是,真的,这对他们有什么好处呢?任何与此不同的变化(比如select count(*) from MyTable where Category = 5)都需要全表扫描(或索引扫描)并且是 O(N)。

                【讨论】:

                • 我知道它可以,我在问他们是否这样做,如果不是(据我了解),为什么不。
                • 不,他们不应该存储行数,看我的回答。
                • 咳咳……“然而……”这一行是答案的重要部分。
                【解决方案10】:

                使用 Informix,在没有 LBAC(基于标签的访问控制)等复杂因素的情况下,SELECT COUNT(*) FROM SomeTable 为 O(1);它从它维护的控制信息中提取信息。如果存在 WHERE 子句或 LBAC,或者表是视图或许多其他因素中的任何一个,则它不再是 O(1)。

                【讨论】:

                  【解决方案11】:

                  在 PostgreSQL 上显然是 O(N):

                  => explain select count(*) from tests;
                                           QUERY PLAN                              
                  ---------------------------------------------------------------------
                  Aggregate  (cost=37457.88..37457.89 rows=1 width=0)
                    ->  Seq Scan on tests  (cost=0.00..33598.30 rows=1543830 width=0)
                  (2 rows)
                  

                  (Seq Scan意味着它必须扫描整个表)

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-05-09
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-09-10
                    • 2021-07-20
                    相关资源
                    最近更新 更多