count(*) 比 count(1) 快
本期播客
PostgreSQL 19 preview - count(*) 比 count(1) 快
PG 19 优化count(表达式)性能, 当表达式中包含非空列, 且不包含order by或distinct时, 将转换为count(*). 从而提升性能.
为什么能提升性能, 主要是表达式的计算和deform tuple(解构元组)以提取列值的开销. 如果是count(*)可以完全规避计算和deform.
https://github.com/postgres/postgres/commit/42473b3b31238b15cc3c030b4416b2ee79508d8c
Commit 42473b3
david-rowley
david-rowley
committed
9 hours ago
·
Have the planner replace COUNT(ANY) with COUNT(*), when possible
This adds SupportRequestSimplifyAggref to allow pg_proc.prosupport
functions to receive an Aggref and allow them to determine if there is a
way that the Aggref call can be optimized.
Also added is a support function to allow transformation of COUNT(ANY)
into COUNT(*). This is possible to do when the given "ANY" cannot be
NULL and also that there are no ORDER BY / DISTINCT clauses within the
Aggref. This is a useful transformation to do as it is common that
people write COUNT(1), which until now has added unneeded overhead.
When counting a NOT NULL column. The overheads can be worse as that
might mean deforming more of the tuple, whichfor large fact tables may
be many columns in.
It may be possible to add prosupport functionsfor other aggregates. We
could consider if ORDER BY could be dropped for some calls, e.g. the
ORDER BY is quite useless in MAX(c ORDER BY c).
There is a little bit of passing fallout from adjusting
expr_is_nonnullable() to handle Const which results in a plan change in
the aggregates.out regression test. Previously, nothing was able to
determine that "One-Time Filter: (100 IS NOT NULL)" was always true,
therefore useless to include in the plan.
Author: David Rowley <[email protected]>
Reviewed-by: Corey Huinker <[email protected]>
Reviewed-by: Matheus Alcantara <[email protected]>
Discussion: https://postgr.es/m/CAApHDvqGcPTagXpKfH=CrmHBqALpziThJEDs_MrPqjKVeDF9wA@mail.gmail.com
详细解读
这个 PostgreSQL 提交(Commit 42473b3b31238b15cc3c030b4416b2ee79508d8c)的主要目的是对聚合函数 COUNT() 进行性能优化,特别是当它被用于对非空值进行计数时。
该补丁的标题是:
"Have the planner replace COUNT(ANY) with COUNT(*), when possible"
(让规划器在可能的情况下,将 COUNT(ANY) 替换为 COUNT(*))
以下是该补丁的关键解读:
1. 核心优化:COUNT(非空表达式) 转换为 COUNT(*)
问题背景: 在 SQL 中, COUNT(expression)只会计算expression结果为非 NULL 的行数,而COUNT(*)总是计算所有行数。如果用户对一个保证非空 (NOT NULL) 的列或表达式进行计数(例如COUNT(1)或COUNT(非空列)),那么COUNT(expression)的结果与COUNT(*)是相同的。性能瓶颈: 即使是像 COUNT(1)这样看起来简单的表达式,以前 PostgreSQL 也需要额外的开销来计算这个表达式的值(例如1),并检查它是否为NULL。对于包含许多列的大型表,这个过程可能涉及“解构元组 (deforming the tuple)”以提取列值,从而带来不必要的性能开销。补丁实现: 这个补丁引入了一个优化,允许查询规划器在以下条件满足时,将 COUNT(expression)自动重写为更高效的COUNT(*):
expression保证非 NULL。COUNT()调用中不包含ORDER BY或DISTINCT子句。
2. 实现机制:引入 SupportRequestSimplifyAggref
为了实现这种优化,补丁引入了一种新的支持请求类型: SupportRequestSimplifyAggref。这允许 pg_proc.prosupport函数(用于定义聚合函数支持逻辑的函数)接收一个Aggref节点,并检查该聚合调用是否有优化的潜力,例如简化。补丁为 COUNT()添加了一个支持函数,专门用来执行上述的COUNT(ANY)到COUNT(*)的转换逻辑。
3. 附带的优化和影响
expr_is_nonnullable()改进: 补丁同时调整了expr_is_nonnullable()函数,使其能够正确处理常量值 (Const)。这意味着规划器现在可以识别像100 IS NOT NULL这样的常量表达式总是为真,并将其视为冗余。执行计划变化: 这种改进导致某些执行计划变得更简洁。例如,在回归测试中,先前计划中包含的无用过滤器 "One-Time Filter: (100 IS NOT NULL)"被移除,因为规划器现在知道这个过滤器没有实际作用。
简而言之,这是一个重要的性能补丁,通过在查询规划阶段将冗余的 COUNT(非空) 转换为最高效的 COUNT(*),从而减少了不必要的表达式评估和 NULL 值检查开销。