【发布时间】:2010-09-27 08:20:12
【问题描述】:
我了解计算 SQL 表中行数的最佳方法是 count(*)(或等效的 count(PrimaryKey))。
- 这是 O(1) 吗?
- 如果没有,为什么不呢?
为什么不直接实现一个计数器并为这个特定的查询返回它呢?是不是因为这个查询不是一个常见的用例?
如果答案因 SQL 引擎而异,我想听听不同之处 - 但无论如何,我对生产 SQL 引擎中的实际实现感兴趣。
【问题讨论】:
标签: sql performance count
我了解计算 SQL 表中行数的最佳方法是 count(*)(或等效的 count(PrimaryKey))。
为什么不直接实现一个计数器并为这个特定的查询返回它呢?是不是因为这个查询不是一个常见的用例?
如果答案因 SQL 引擎而异,我想听听不同之处 - 但无论如何,我对生产 SQL 引擎中的实际实现感兴趣。
【问题讨论】:
标签: sql performance count
在 一些 RDBM 中,这是 O(1)(最显着的是 MySQL),在 AFAIK 中,它通常不受欢迎,并被认为是“丑陋的性能黑客”。原因是,如果您有事务(每个真正的 RDBM 都应该有),那么表中的总行数可能等于也可能不等于总行数您可以从当前事务中看到。这就是为什么服务器需要检查哪些行对您的事务实际可见,使其 O(n) 多于 O(1)。
如果您想优化获取行数的过程并对近似结果感到满意,大多数 RDBM 都有特殊的“信息”表,其中包含有关表的信息,包括大致的行数(同样,它是不是确切的行数,因为事务)。
【讨论】:
不,这不是一个常见的用例。我见过的大多数行数都涉及到一些 where 子句。
这未实现的主要原因是行计数器将成为多用户环境中争用的原因。每次插入或删除一行时,计数器都需要更新,有效地为每次插入/删除锁定整个表。
【讨论】:
基于索引或表的 COUNT(*) 的性能实际上取决于段大小。你可以有一个只有一行的 1GB 表,但 Oracle 可能仍然需要扫描整个分配的空间。如果不改变高水位线,再插入一百万行可能根本不会影响性能。索引以类似的方式工作,不同的删除模式可能会在索引结构中留下不同数量的可用空间,并导致索引扫描提供比 O(N) 更好或更差的性能。
所以,理论上它是 O(N)。在实践中,存在可能导致其非常不同的实施问题。
例如,在某些情况下,Oracle 数据仓库的性能可能优于 O(N)。特别是优化器可以扫描位图索引,位图索引的大小与表的大小只有微弱的关系,这与 b 树索引不同。这是因为压缩方法使索引大小取决于表的大小、唯一值的数量、整个表中值的分布以及我相信的历史加载模式。因此,将表中的行数增加一倍可能只会使索引大小增加 10%。
在存在物化视图的情况下,您也可能通过读取汇总表得到 O(1)(触发器是一种不安全的做法)。
【讨论】:
在 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 更新索引统计信息的频率,这个数字可能会略有偏差,但如果您需要一个大致的数字,而不是一个确切的数字,那么这些非常有用。
【讨论】:
这不是固定时间,因为在事务引擎中需要检查当前事务中存在多少行,这通常涉及全表扫描。
不带 where 子句的 COUNT(*) 优化对于数据库来说并不是一个特别有用的优化,它以牺牲其他东西为代价;大表的用户很少做这样的查询,如果存在 WHERE 子句,这将毫无帮助。
MySQL 中的 MyISAM 通过存储确切的行数来“欺骗”,但它只能这样做,因为它没有 MVCC,因此不需要担心行在哪些事务中。
【讨论】:
对于 Oracle,它通常为 O(N),除非查询结果在缓存中, 因为它本质上必须迭代所有块,或者迭代索引来计算它们。
【讨论】:
通常是 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
【讨论】:
SQL Server 中有一个(不准确的)快捷方式,您可以在其中查看元数据 sys.partitions 中特定对象(如表上的索引)的计数。
操作是 O(1),但只是一个估计。
【讨论】:
数据库可以存储表中的行数并响应 O(1)
select count(*) From MyTable
但是,真的,这对他们有什么好处呢?任何与此不同的变化(比如select count(*) from MyTable where Category = 5)都需要全表扫描(或索引扫描)并且是 O(N)。
【讨论】:
使用 Informix,在没有 LBAC(基于标签的访问控制)等复杂因素的情况下,SELECT COUNT(*) FROM SomeTable 为 O(1);它从它维护的控制信息中提取信息。如果存在 WHERE 子句或 LBAC,或者表是视图或许多其他因素中的任何一个,则它不再是 O(1)。
【讨论】:
在 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意味着它必须扫描整个表)
【讨论】: