【最佳实践】一文读懂如何书写性能最优的自定义函数

举报
leapdb 发表于 2025/12/05 19:58:07 2025/12/05
【摘要】 如何书写性能最优的自定义函数

函数开发的细节请查询产品文档,这里对特别需要注意的地方重点进行解释。

函数属性介绍

易变性

◾VOLATILE(默认): 表示函数返回值在一次表扫描内随时可以改变,例如:random, currval, timeofday等。这也导致这些函数不会被做任何优化。

◾STABLE: 表示对相同函数入参,在同一次表扫描里,该函数的返回值不会变,但是在同一会话的不同SQL多次调用时可能会变化。

◾IMMUTABLE: 表示该函数在相同入参时总是返回同样的结果。

也就是说该函数不会通过查询数据库,GUC参数,随机数,当前时间等可能不确定性的因素来生成函数返回值。如果入参是常量,这类函数可以直接被优化器替换为函数值,减少函数调用。

**注意:**IMMUTABLE函数会被自动下推到DN执行,如果用户错误定义了函数的IMMUTABLE属性,那么可能会导致结果集错误。因此,用户在指定函数的属性为IMMUTABLE的时候,要特别慎重。

不可定义IMMUTABLE的几种情况:

1.函数中引用了表,视图等对象,因为当表的数据发生变化的时候,函数的返回值可能发生变化。
2.函数中引用了STABLE/VOALATILE类型的函数,那么该函数不能定义为IMMUTABLE3.函数中有不下推的因素,因为IMMUTABLE意味着要下推到DN执行,与函数体内部的不下推因素相互冲突。典型场景例如,包含不下推的函数、语法等。
4.函数中含有聚合运算,但聚合运算的运算需要生成STREAM计划才能完成计算的(部分结果在DN计算,部分结果在CN计算,例如listagg函数等)。

为了防止错误定义IMMUTABLE可能导致严重问题,数据库内部可以通过设置behavior_compat_options=‘check_function_conflicts’来开启对函数定义冲突的检查,目前可以识别出上述1和2场景。

下推属性

◾SHIPPABLE,表示该函数可以下推到DN上执行。
◾NOT SHIPPABLE(默认),表示该函数不可以下推到DN上执行。

注意:
对于IMMUTABLE函数,不管SHIPPABLE如何设置,函数始终可以下推到DN上执行。
对于STABLE/VOLATILE函数,当且仅当函数属性是SHIPPABLE的时候,函数才可以下推到DN执行。
定义SHIPPABLE属性时需特别慎重,SHIPPABLE意味着整个函数会下推到DN上执行,如果设置不当,会导致结果错误等严重问题。

与定义IMMUTABLE属性一样,SHIPPABLE属性的定义也有诸多约束,简单来说就是函数体内不能有不可下推的因素,函数下推到单DN执行后,函数内部的计算逻辑仅依赖当前DN的数据集合。举例如下:

1.如果函数内部引用了表,并且表为HASH分布,那么该函数通常不能定义为SHIPPABLE2.函数内部有不可下推的因素,函数,语法等,那么该函数不能定义为SHIPPABLE,可参考语句下推调优。
3.函数内部的计算过程可能需要跨DN数据,这种情况该函数通常不能定义为SHIPPABLE,例如一些聚合运算等。

入参含NULL处理

◾CALLED ON NULL INPUT(默认)
表明该函数的某些参数是NULL的时候可以按照正常的方式调用该函数,函数开发者需自己做判空处理。

◾RETURNS NULL ON NULL INPUT或STRICT
如果声明了这个参数,当有NULL值参数时该函数不会被执行;而只是自动返回一个NULL结果。

RETURNS NULL ON NULL INPUT和STRICT的功能相同。
入参含NULL返回值也为NULL的,尽量显示定义STRICT。

保护模式

声明用户定义的C函数是否在保护模式下执行。

◾FENCED模式(默认),则函数在新fork的进程执行,这样函数的异常不会影响CN或者DN进程。
◾NOT FENCED模式,函数的执行在CN或者DN进程中进行;

如何选择:

正在开发或者调试的Function使用FENCED模式。开发测试完成,使用NOT FENCED模式执行,减少fork进程以及通信的开销。
复杂的操作系统操作,例:打开文件,信号处理,线程处理等操作,使用FENCED模式。否则可能影响GaussDB(DWS)数据库的执行。
C函数必须是FENCED模式

执行角色

◾SECURITY INVOKER或AUTHID CURRENT_USER(默认)
表明该函数将带着调用它的用户的权限执行。该参数可以省略。

◾SECURITY DEFINER或AUTHID DEFINER
声明该函数将以创建它的用户的权限执行。

注意:一般默认情况下函数属性均为SECURITY INVOKER。只有特殊场景下才会使用SECURITY DEFINER,比如函数体内有不需要函数调用者关注的敏感数据。

如何写出性能最优的函数

inline优化

类似C++中的inline能力,如果是简短的计算或转换函数,正确的书写为满足inline特征的函数,则优化器可将函数调用优化成表达式执行,明显提升执行性能。
例如:

CREATE FUNCTION func_add_sql(integer, integer) RETURNS integer
AS 'select $1 + $2;'
LANGUAGE SQL IMMUTABLE;

可被优化器自动优化为 $1 + $2 的表达式。
下面执行计划输出的Output已不再是函数调用,而是优化为(a+b)表达式。

postgres=# explain verbose select func_add_sql(a, b) from t1;
                                        QUERY PLAN                                        
------------------------------------------------------------------------------------------
  id |          operation           | E-rows | E-distinct | E-memory | E-width | E-costs 
 ----+------------------------------+--------+------------+----------+---------+---------
   1 | ->  Streaming (type: GATHER) |      1 |            |          |       8 | 9.01    
   2 |    ->  Seq Scan on public.t1 |      1 |            | 1MB      |       8 | 1.01    

      Targetlist Information (identified by plan id)     
 --------------------------------------------------------
   1 --Streaming (type: GATHER)
         Output: ((a + b))
         Node/s: All datanodes (node_group, bucket:16384)
   2 --Seq Scan on public.t1
         Output: (a + b)

inline函数要求:

1. 函数类型,LANGUAGE必须是SQL类型。
2. 函数易变性,不能是VOLATILE,且易变性不高于函数体内的语句。例如:函数体内调用的函数其易变性为STABLE,则该函数只能是STABLE,不能是IMMUTABLE。从高到底按照IMMUTABLE STABLE VOLATILE排序。
3. 函数strict属性,必须与函数体内包含的函数strict一致。
4. 函数体,函数中必须为简单的单条select语句,不含group by等复杂逻辑。
5. 函数返回值,函数内语句返回类型必须与该函数返回类型一致,且不能是set和record等复杂类型。

入参含NULL优化

入参含NULL返回值也肯定为NULL的函数,要显示定义STRICT属性。这样当入参含NULL时,可以减少函数体的调用执行。

函数类型选择

从性能方面考虑,尽量按照 SQL函数 > C函数 > plpgsql函数 顺序选择函数类型。

SQL函数
简单计算优先使用SQL类型,尽量满足inline特征。
C函数
非单条语句的函数建议定义为C函数

申请的内存要用完及放
保护模式必须显示定义为DENCED模式
入参和返回值类型严格注意,避免类型不一致或强制转换导致core或结果集问题。

plpgsql函数
逻辑复杂C函数实现较为困难的可以选择plpgsql类型

函数内设置GUC变量优化

尽量避免在函数内部设置环境变量,SET会多CN执行,造成不必要的通信开销。

CREATE OR REPLACE FUNCTION func_increment_plsql(i integer) RETURNS integer 
SET autoanalyze=off --放这里,不会跨CN进行设置
AS $$  
BEGIN
    SET autoanalyze=off; --放这里,会跨CN进行设置,有通信开销
    RETURN i + 1;
END;
$$ LANGUAGE plpgsql;

VOLATILE的SQL函数优化

VOLATILE属性的SQL语言函数会频繁访问GTM,性能差,且给GTM造成比较大的压力。
因此,LANGUAGE为SQL的函数,尽量不要定义为VOLATILE。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。