在群里被啪啪打脸, AI 纠正了我的自负与愚蠢!
本期播客
AI 纠正了我的愚蠢: 如何让PG自定义聚合函数的finalfunc仅执行一次?
今天, IF club 群里发了一个问题: 如何让PostgreSQL自定义聚合函数的finalfunc仅执行一次?
在我的碳基神经元还停留在过去式, 记忆也出现了混乱, 也没有去看最新文档(没有使用RAG)的情况下, 非常自信的回答了:
“应该是该聚合函数没有实现 parallel (PS: 其实是需要实现 COMBINEFUNC, 将不同worker的中间结果进行合并的函数), 所以会多次调用finalfunc(PS: 这是错误的). 只要在定义agg func时设置为 parallel unsafe 即可!”
大错特错, 我蠢到家了. AI 狠狠的给了我一巴掌.
PS: 这些是我之前写的一些PG agg相关的文章, 仅供参考, 如果你想知道聚合的内部运算逻辑, 可以看一下:
《PostgreSQL 10 自定义并行计算聚合函数的原理与实践 - (含array_agg合并多个数组为单个一元数组的例子)》 《PostgreSQL 多个数组聚合为一维数组加速(array_agg)》 《PostgreSQL Oracle 兼容性之 - 自定义并行聚合函数 PARALLEL_ENABLE AGGREGATE》 《Greenplum 支持人为多阶段聚合的方法 - 直连segment(PGOPTIONS='-c gp_session_role=utility') Or gp_dist_random('gp_id') Or 多阶段聚合 prefunc》 《Postgres-XC customized aggregate introduction》 《PostgreSQL aggregate function customize》
AI 的牛逼在于它的神经元比我多多了, 同时它训练的素材也比我多多了, 并且它还有基于实时资料库的RAG, 我在AI面前就是弱鸡!
AI 纠正了我的错误
要让自定义聚合函数的finalfunc只执行一次,需要从聚合状态共享和finalfunc属性两个方面进行控制。
主要方法
1. 设置FINALFUNC_MODIFY为READ_ONLY
在创建聚合函数时指定finalfunc为只读模式:
CREATEAGGREGATE my_aggregate(input_type) (
SFUNC = transition_function,
STYPE = transition_type,
FINALFUNC = final_function,
FINALFUNC_MODIFY = READ_ONLY
);
FINALFUNC_MODIFY = READ_ONLY表示finalfunc不修改其参数,这是默认值(除了有序集合聚合) 。
2. 禁用聚合状态共享
通过设置shareable = false来防止多个聚合共享同一个状态值 :
// 在聚合函数的内部实现中
peragg->shareable = false;
3. 在finalfunc内部实现执行控制
在finalfunc中添加状态检查逻辑,确保只执行一次:
Datum
my_finalfunc(PG_FUNCTION_ARGS)
{
staticbool executed = false;
if (executed)
PG_RETURN_NULL();
// 执行实际的final逻辑
executed = true;
// ... 你的处理逻辑
PG_RETURN_DATUM(result);
}
执行机制分析
PostgreSQL的聚合执行流程中,finalfunc在finalize_aggregate函数中被调用 :
每个聚合组处理完所有输入行后调用一次finalfunc 如果存在多个相同的聚合引用,它们会共享状态(当shareable=true时) finalfunc可能被重新执行,如 array_agg_finalfn的注释所示
最佳实践
完整的聚合函数创建示例
-- 转换函数
CREATEFUNCTION my_transfn(internal, integer)
RETURNS internal AS'MODULE_PATHNAME'LANGUAGE C;
-- 最终函数
CREATEFUNCTION my_finalfn(internal)
RETURNSintegerAS'MODULE_PATHNAME'LANGUAGE C IMMUTABLE;
-- 创建聚合,确保finalfunc只执行一次
CREATEAGGREGATE my_agg(integer) (
SFUNC = my_transfn,
STYPE = internal,
FINALFUNC = my_finalfunc,
FINALFUNC_MODIFY = READ_ONLY,
PARALLEL = SAFE
);
测试验证
可以使用测试用例验证finalfunc的执行次数 :
-- 在transfn和finalfn中添加NOTICE来观察调用次数
CREATEFUNCTION test_transfn(state int4, n int4) RETURNS int4 AS $$
BEGIN
RAISENOTICE'transfn called with %', n;
RETURN COALESCE(state, 0) + n;
END;
$$ LANGUAGE plpgsql;
CREATEFUNCTION test_finalfn(state int4) RETURNS int4 AS $$
BEGIN
RAISENOTICE'finalfn called with %', state;
RETURN state;
END;
$$ LANGUAGE plpgsql;
Notes
FINALFUNC_MODIFY有三个选项:READ_ONLY、SHAREABLE、READ_WRITE当finalfunc需要修改状态时,必须设置为READ_WRITE,这会阻止状态共享 有序集合聚合的默认FINALFUNC_MODIFY是READ_WRITE 聚合状态共享优化在 AggInfo结构中控制
相关代码:
doc/src/sgml/ref/create_aggregate.sgml
<term><literal>FINALFUNC_MODIFY</literal> = { <literal>READ_ONLY</literal> | <literal>SHAREABLE</literal> | <literal>READ_WRITE</literal> }</term>
<listitem>
<para>
This option specifies whether the final function is a pure function
that does not modify its arguments. <literal>READ_ONLY</literal> indicates
it does not; the other two values indicate that it may change the
transition state value. See <xref linkend="sql-createaggregate-notes"/>
below for more detail. The
default is <literal>READ_ONLY</literal>, except for ordered-set aggregates,
forwhich the default is <literal>READ_WRITE</literal>.
</para>
src/include/executor/nodeAgg.h
* "shareable" is falseif this agg cannot share state values with other
* aggregates because the final function is read-write.
*/
bool shareable;