count(column_name)
并非不准确,它只是与 count(*) 完全不同的东西。
SQL 标准定义count(column_name)
为等效于count(*) where column_name IS NOT NULL
. 如果 column_name 可以为空,结果必然会有所不同。
在 Oracle(可能还有其他 DBMS)中,count(*)
将使用非空列上的可用索引来计算行数(例如 PK 索引)。所以它会一样快
此外,没有什么类似于 SQL Server 或 MySQL 中的 rowid (在 PostgreSQL 中应该是ctid
)。
使用count(*)
. 这是获取行数的最佳选择。如果有足够的索引可用,则让 DBMS 在后台进行任何优化。
编辑
关于 Oracle 如何自动使用索引(如果可用)以及如何减少数据库完成的工作量的快速演示:
测试台的设置:
create table foo (id integer not null, c1 varchar(2000), c2 varchar(2000));
insert into foo (id, c1, c2)
select lvl, c1, c1 from
(
select level as lvl, dbms_random.string('A', 2000) as c1
from dual
connect by level < 10000
);
这会生成 10000 行,每行填充一些空间,以确保表具有实际大小。
现在在 SQL*Plus 中,我运行以下命令:
SQL> set autotrace traceonly 解释统计;
SQL> 从 foo 中选择 count(*);
执行计划
-------------------------------------------------- --------
计划哈希值:1342139204
-------------------------------------------------- -----------------
| 身份证 | 操作 | 姓名 | 行 | 成本 (%CPU)| 时间 |
-------------------------------------------------- -----------------
| 0 | 选择声明 | | 1 | 2740 (1)| 00:00:33 |
| 1 | 排序聚合 | | 1 | | |
| 2 | 表访问已满| 福 | 9999 | 2740 (1)| 00:00:33 |
-------------------------------------------------- -----------------
统计数据
-------------------------------------------------- --------
181 次递归调用
0 db 块获取
10130 一致获得
0 次物理读取
0 重做大小
通过 SQL*Net 向客户端发送 430 字节
通过 SQL*Net 从客户端收到 420 字节
2 次 SQL*Net 往返客户端
5种(记忆)
0 种(磁盘)
已处理 1 行
SQL>
如您所见,对需要 10130 次“IO 操作”的表进行全表扫描(我知道这不是正确的术语,但为了演示,对于从未见过的人来说,这应该是一个足够好的解释前)
现在我在该列上创建一个索引并再次运行 count(*):
SQL> 在 foo (id) 上创建索引 i1;
已创建索引。
SQL> 从 foo 中选择 count(*);
执行计划
-------------------------------------------------- --------
计划哈希值:129980005
-------------------------------------------------- --------------------
| 身份证 | 操作 | 姓名 | 行 | 成本 (%CPU)| 时间 |
-------------------------------------------------- --------------------
| 0 | 选择声明 | | 1 | 7 (0)| 00:00:01 |
| 1 | 排序聚合 | | 1 | | |
| 2 | 索引快速全扫描| I1 | 9999 | 7 (0)| 00:00:01 |
-------------------------------------------------- --------------------
统计数据
-------------------------------------------------- --------
1 递归调用
0 db 块获取
27连胜
21 次物理读取
0 重做大小
通过 SQL*Net 向客户端发送 430 字节
通过 SQL*Net 从客户端收到 420 字节
2 次 SQL*Net 往返客户端
0 种(内存)
0 种(磁盘)
已处理 1 行
SQL>
正如您所看到的,Oracle 确实使用了 (not null!) 列上的索引,并且 IO 的数量急剧下降(从 10130 到 27 - 这不是我所说的“非常低效”)。
“物理读取”源于索引刚刚创建且尚未在缓存中的事实。
我希望其他 DBMS 应用相同的优化。