在 PostgreSQL 19 的规划中,一项关键的优化是针对 COUNT(表达式) 查询的性能提升。当表达式是一个非空列,并且不包含 ORDER BY 或 DISTINCT 子句时,查询规划器会将其自动转换为 COUNT(*),从而显著提升执行效率。
那么,性能提升的来源是什么?主要是消除了表达式的计算开销以及为提取列值而解构元组 (deform tuple) 的开销。如果直接使用 COUNT(*),则可以完全规避这些不必要的计算和解构过程。
相关的提交记录位于 GitHub: 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, which for large fact tables may
be many columns in.
It may be possible to add prosupport functions for 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 <dgrowleyml@gmail.com>
Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Discussion: https://postgr.es/m/CAApHDvqGcPTagXpKfH=CrmHBqALpziThJEDs_MrPqjKVeDF9wA@mail.gmail.com
详细解读
这个 PostgreSQL 提交的主要目的是对聚合函数 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 值检查开销。对于需要处理海量数据的应用来说,这类优化能积少成多,带来可观的效率提升。想了解更多类似的技术解析和优化实践,欢迎访问 云栈社区 进行探讨。
|