作者简介

张连壮,多年PostgreSQL数据库内核研发经验,高可用/数据复制方面经验较为丰富,目前主要从事分布式数据库Citus相关工作,CitusDB中国【站主】专注于Citus技术分享的全信息平台,欢迎大家参与到Citus知识传播和Citus使用推广的活动中!!!

背景

count(* )在PostgreSQL上经常被抱怨执行非常慢。而在一段时间中我收到过很多count(1 )比count(* )快的说法,并将这种理解此应用于我的工作中。

为什么count(*)这么慢?

l 大多数人都会接受以下SQL很慢:

SELECT count(*)FROM /* 复杂查询 */;

这是一个复杂的查询,PostgreSQL必须知道它需要将多少行的数据用于计算。

l 但如果以下情况很慢,很多人都会感到震惊:

SELECT count(*) FROM /* 大表 */;

由于表中没有存储“行数”(像在MySQL中的MyISAM一样),PostgreSQL计算行的唯一方法是获得全部数据。

因此,count(*)通常会执行表的顺序扫描,这个代价非常昂贵。

问题出在 count( * )中的 * 么?

select * from 中的 * 将扩展表的所有列,因此,许多人认为使用count( * )效率低下,应该写count(id)或count(1)代替。但是count( * )中的 * 与select * 中的 * 是完全不同的,count * 中的 * 仅仅代表row并不会展开它,写入count(1)与count( * )是相同的效果。但是count(id)有些不同,它只计算id是NOT NULL的行数。因此避免count( * )没有任何用处,反而count( * ) 的速度还会更快。

源码解读

l 查看count函数定义

test=# select proname, pronargs, prosrc from pg_proc where proname='count';proname | pronargs | prosrc ---------+----------+-----------------count | 0 | aggregate_dummycount | 1 | aggregate_dummy(2 rows)

l 什么? 0个参数的count?

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个参数的标识使用!!!

l count查询

我们来看看几种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的时间几乎是一样的。

l 计算cpu单核运算力的小程序

[lzzhang@lzzhang-pc ~]$ cat test.c #include #include #include #include unsigned long long counter=1;void signal_handler(int sig){

printf("counter %llu \n", counter); fflush(stdout);exit(0);}int main(int argc, char *argv[]){ signal(SIGALRM, signal_handler);alarm(1);while(counter++);return 0;}[lzzhang@lzzhang-pc ~]$ gcc test.c[lzzhang@lzzhang-pc ~]$ ./a.out counter 657214986

提升PostgreSQL Count 性能

仅使用索引扫描

扫描一个小索引比全表扫描会减少很多代价,但是,由于PostgreSQL是多版本并发控制,在PostgreSQL中并不是这么简单的可以完成。每个行版本(“tuple”)包含可见的数据快照的信息。但是这些信息不会存储在索引中。因此,计算索引中的条目通常是不够的,因为PostgreSQL必须访问表元组以确保索引条目可见。

为了缓解这个问题,PostgreSQL引入了visibility map,这是一种数据结构,用于存储表块中的所有元组是否对所有人可见。

如果大多数表块都是可见的,则索引扫描不需要访问表元组来确定可见性。这种索引扫描称为“index only scan”,因此扫描索引以计算行数通常更快。

vacuum会更新visibility map,因此如果要使用索引来加速count * 的计算,请确保autovacuum在表上运行得足够频繁。

真的需要吗count(* )么?

有时最好的解决方案是寻找替代方案。

如果您不需要精确计数可以使用近似值。在这种情况下,您可以使用PostgreSQL用于查询计划的估计值:

SELECT reltuples::bigintFROM pg_catalog.pg_classWHERE relname = 'mytable';

这个值由autovacuum和autoanalyze更新,所以它永远不会超过10%的误差。您可以减少autovacuum_analyze_scale_factor以便autoanalyze更频繁地运行。

使用辅助表

我们创建mytable_count用于记录表的行数,并在mytable修改数据时使用触发器更新mytable_count的值。

START TRANSACTION;CREATE TABLE mytable_count(c bigint);

CREATE FUNCTION mytable_count() RETURNS triggerLANGUAGE plpgsql AS$$BEGINIF TG_OP = 'INSERT' THENUPDATE mytable_count SET c = c + 1; RETURN NEW; ELSIF TG_OP = 'DELETE' THEN UPDATE mytable_count SET c = c - 1; RETURN OLD; ELSE UPDATE mytable_count SET c = 0; RETURN NULL; END IF;END;$$;

CREATE TRIGGER mytable_count_mod AFTER INSERT OR DELETE ON mytableFOR EACH ROW EXECUTE PROCEDURE mytable_count();-- TRUNCATE triggers must be FOR EACH STATEMENTCREATE TRIGGER mytable_count_trunc AFTER TRUNCATE ON mytableFOR EACH STATEMENT EXECUTE PROCEDURE mytable_count();-- initialize the counter tableINSERT INTO mytable_countSELECT count(*) FROM mytable;COMMIT;

我们在单个事务中执行所有操作,以便不会“丢失”任何数据,因为CREATE TRIGGER是SHARE ROW EXCLUSIVE的锁,这可以防止所有并发修改,当然,缺点是所有并发数据修改必须等到SELECT count(* )完成。

这为我们提供了一个非常快速替代count(* )的方案,但代价是减慢了对表的数据修改性能。即使这个计数表不停的更新,也没有“表膨胀”的危险,因为这些都是HOT更新。

PostgreSQL中文社区欢迎广大技术人员投稿

投稿邮箱:press@postgres.cn

sql count(1) count(*)区别_PostgreSQL的count(1)真的比count(*)快么?相关推荐

  1. sql中 count(*),count(1)以及count(字段)的区别

    前言 记得很早以前就听说,在使用count的时候要用count(1)而不要用count(*),因为使用count(*)的时候会对所有的列进行扫描,相比而言count(1)不用扫描所有列,所以count ...

  2. 关于数据库优化1——关于count(1),count(*),和count(列名)的区别,和关于表中字段顺序的问题...

    1.关于count(1),count(*),和count(列名)的区别 相信大家总是在工作中,或者是学习中对于count()的到底怎么用更快.一直有很大的疑问,有的人说count(*)更快,也有的人说 ...

  3. mysql下count(*)和count(1)的区别

    2019独角兽企业重金招聘Python工程师标准>>> 今天看公司项目发现了一个奇怪sql写法 select count(8) from .... 这也许是开发人员不小心或者是习惯把 ...

  4. mysql select count() count(1)_select count()和select count(1)的区别和执行方式讲解

    select count()和select count(1)的区别和执行方式讲解 发布时间:2020-09-06 13:26:14 来源:脚本之家 阅读:227 作者:CODETC 在SQL Serv ...

  5. mysql count里select_select count()和select count(1)的区别和执行方式讲解

    在SQL Server中Count(*)或者Count(1)或者Count([列])或许是最常用的聚合函数.很多人其实对这三者之间是区分不清的.本文会阐述这三者的作用,关系以及背后的原理. 往常我经常 ...

  6. MySQL的count(*) ,count(1),count(id)的区别

    数据库查询相信很多人都不陌生,所有经常有人调侃程序员就是CRUD专员,这所谓的CRUD指的就是数据库的增删改查. 在数据库的增删改查操作中,使用最频繁的就是查询操作.而在所有查询操作中,统计数量操作更 ...

  7. count(*) count(1)与count(字段)的区别

    话不多说 MySql官方文档聚合函数地址 首先count函数的简单介绍: 上翻译: 1.COUNT(expr) ,返回SELECT语句检索的行中expr的值不为NULL的数量.结果是一个BIGINT值 ...

  8. count(*)和count(1)的区别

    个人总结如下: 一.count(*)和count(1)查询速度 使用count函数,当要统计的数量比较大时,发现count(*)花费的时间比较多,相对来说count(1)花费的时间比较少. 1.如果你 ...

  9. Mysql中的count()与sum()区别

    Mysql中的count()与sum()区别 首先创建个表说明问题 CREATE TABLE `result` ( `name` varchar(20) default NULL, `subject` ...

  10. MySQL COUNT函数优化及count(1)/count(*)/count(列名)的区别

    count函数优化 使用近似值: 在某些应用场景中,不需要完全精确的值,可以参考使用近似值来代替,比如可以使用explain来获取近似的值.其实在很多OLAP的应用中,需要计算某一个列值的基数,有一个 ...

最新文章

  1. 融合机器人技术和神经科学的神经工程未来与挑战
  2. ActiveSync合作关系对话框的配置
  3. popover带箭头弹框
  4. 记录 之 遇到的 lamda 表达式和功能理解
  5. gitlab 构建tag_GitLab常用命令 分支 Tag 配置 操作
  6. 4. 2D绘制与控件绘制
  7. 获取程序下基目录下的文件的
  8. 配置管理小报110221:在linux上用真实帐号发mail的方法
  9. 杭电2531Catch him
  10. dpg learning 和q_深度学习和强化学习之间的差别有多大?
  11. 用户画像数据建模方法
  12. 文件同步工具 GoodSync Enterprise 破解
  13. 多x多y的origin图_3本纯爱文推荐:医疗师 x 小狼狗,漫漫何其多经典
  14. 计算机考试没来得及关掉文档,计算机二级考试挽回受损WORD文档的方法
  15. SurfacePro6解决亮度自动调节问题
  16. BZOJ5287 HNOI2018毒瘤
  17. 产业园区需要塑造的“六”大品牌
  18. TypesScript类型注解
  19. 记一次内网kafka映射到外网端口遇到小问题
  20. logistic模型预测人口python_基于logistic回归stats模型的概率预测置信区间

热门文章

  1. QT的QGLFunctions类的使用
  2. c++设计模式编程基础
  3. 怎么用matlab处理数据,如何用Matlab处理.wfm格式的数据
  4. Flink-Table StreamTableEnvironment基础知识
  5. 06_1.Pytorch中如何表示字符串、word embedding、One - hot、Embedding(Word2vec、BERT、Glove)【学习总结】
  6. 创建一个存储函数,返回指定员工的姓名,薪水和年收入
  7. 20-javamail
  8. 远程服务器概念,远程服务
  9. 装箱---一个工厂制造的产品形状都是长方体,它们的高度都是 h,长和宽都相等,一共有六个型号,他们的长宽分别为 1*1, 2*2, 3*3, 4*4, 5*5, 6*6.
  10. C++const类型的引用参数