这是杂货铺的第463篇文章
曾经测试过Oracle 11g下count(*)、count(1)、count(主键列)和count(包含空值的列)这几种操作,究竟有何区别,结论如下,
11g下,通过实验结论,说明了count(1)和count(主键索引字段)其实都是执行的count(*),而且会选择索引的FFS扫描方式,count(包含空值的列)这种方式一方面会使用全表扫描,另一方面不会统计空值,因此有可能和业务上的需求就会有冲突,因此使用count统计总量的时候,要根据实际业务需求,来选择合适的方法,避免语义不同。
原文参考:《select count(*)、count(1)、count(主键列)和count(包含空值的列)有何区别?》。
前几天,碰巧看见PostgreSQL中文社区发的一篇文章,关于在PG中count(1)和count(*)的效率问题,从结论看,和Oracle很像,但是他是从开源code,探究的整个过程,能够更准确地了解背后的原理,
写入count(1)与count(*)是相同的效果。 但是count(id)有些不同,它只计算id是NOT NULL的行数。 因此避免count(*)没有任何用处,反而count(*)的速度还会更快。
原文参考《PostgreSQL的count(1)真的比count(*)快么?》。
源码解读
test=# select proname, pronargs, prosrc from pg_proc where proname='count';proname | pronargs | prosrc ---------+----------+-----------------count | 0 | aggregate_dummycount | 1 | aggregate_dummy(2 rows)
test=# select count() from lzzhang;ERROR: count(*) must be used to call a parameterless aggregate function
执行报错,说好的0个参数呢?
查看语法,仅保留主要代码。
* (*) - normal agg with no args * (aggr_arg,...) - normal agg with args * (ORDER BY aggr_arg,...) - ordered-set agg with no direct args
* (aggr_arg,... ORDER BY aggr_arg,...) - ordered-set agg with direct args * * The zero-argument case is spelled with '*' for consistency with COUNT(*).aggr_args: '(' '*' ')' { $$ = list_make2(NIL, makeInteger(-1)); }
count(*)的*唯一的作用仅仅是作为count函数0个参数的标识使用!
我们来看看几种count的情况,
test=# select * from lzzhang;id | id1 | id2 ----+-----+-----1 | | 1 | 1 | 1 | 1 | 12 | |
2 | | 3 | | 3 | | 3 | | (8 rows)
test=# select count(*) from lzzhang;count -------8(1 row)
test=# select count(1) from lzzhang;count -------8(1 row)
test=# select count('const_string') from lzzhang; count ------- 8(1 row)
test=# select count(id) from lzzhang; count ------- 8(1 row)
test=# select count(id1) from lzzhang; count ------- 2(1 row)
这里我们简单分成三种类型的count
1. 列名(id/id1)-只计算非null的数据
2. 无参(*)–计算全部数据
3. 常量(1/const_string)–计算全部数据
count只计算非null的数据。
三种方式在ExecInterpExpr函数中的处理
列名: EEO_CASE(EEOP_OUTER_FETCHSOME) { slot_getsomeattrs(outerslot, op->d.fetch.last_var);
EEO_NEXT(); } 只计算非null的数据
常量: EEO_CASE(EEOP_CONST) { *op->resnull = op->d.constval.isnull;
*op->resvalue = op->d.constval.value;
EEO_NEXT(); } 常量当然不会是null,所以1/const_string会计算全部数据。
无参: EEO_CASE(EEOP_AGG_STRICT_TRANS_CHECK) { AggState *aggstate; AggStatePerGroup pergroup;
aggstate = op->d.agg_strict_trans_check.aggstate; pergroup = &aggstate->all_pergroups [op->d.agg_strict_trans_check.setoff] [op->d.agg_strict_trans_check.transno];
if (unlikely(pergroup->transValueIsNull))
EEO_JUMP(op->d.agg_strict_trans_check.jumpnull);
EEO_NEXT(); } 检查之后就直接计算。
所以*并不会比1快,反而*比1会减少cpu的计算,速度更快!现在cpu的计算速度很快了,我的单核每秒可以计算6.5亿次,所以*和1的时间几乎是一样的。
可见,Oracle和PG对于一些操作,其实存在相同之处的,谈不上谁借鉴,可能更多地还是针对场景,为了满足业务以及性能方面的需求,提供的合理逻辑。其实我想说的是,无论是从10046,还是从PG的源码,我们应该看得深入一些,从原理设计层面,站在架构师、开发人员的视角,了解这个功能,设计的初衷,这才有助于我们借鉴好的设计,学以致用,丰富我们的经验,实现自我提高。