GaussDB(DWS)通用兼容性插件 - sql_dialect的最佳实践

举报
yd_271793565 发表于 2025/06/11 11:39:56 2025/06/11
【摘要】 GaussDB(DWS)通用兼容性插件 - sql_dialectsql_dialect插件是DWS用于支持各种SQL方言的插件包。优先级:方言插件中函数优先级高于数据库内核中的函数,便于解决与内核的冲突。绑定方言:每一个数据库可以独立绑定方言插件,与CREATE DATABASE时选择的DBCOMPATIBILITY没有关联关系,但建议保持一致。修改方言:方言插件一旦绑定,不支持修改,除非...

GaussDB(DWS)通用兼容性插件 - sql_dialect

sql_dialect插件是DWS用于支持各种SQL方言的插件包。


优先级:方言插件中函数优先级高于数据库内核中的函数,便于解决与内核的冲突。
绑定方言:每一个数据库可以独立绑定方言插件,与CREATE DATABASE时选择的DBCOMPATIBILITY没有关联关系,但建议保持一致。
修改方言:方言插件一旦绑定,不支持修改,除非先卸载插件再重新绑定。


插件基础能力

  1. 支持简单SQL实现的函数
  2. 支持plpgsql实现的函数
  3. 支持C实现的函数
  4. 支持独立的升降级,与内核升降级无关
  5. 可以通过管控面平台,快速发布升级


插件包结构

├── lib
│   └── postgresql
│       └── libsql_dialect.so --插件动态库文件
└── share
    └── postgresql
        └── extension
            ├── sql_dialect.control --插件控制文件
            ├── sql_dialect_mysql.sql --mysql方言支持的对象文件
            ├── sql_dialect--1.0.0.sql
            ├── sql_dialect--1.0.0--1.0.1.sql
            └── sql_dialect--1.0.1--1.0.0.sql


插件使用方法

首先确保sql_dialect插件已经正确安装部署。


绑定方言插件

--为当前数据库绑定方言插件
--1. 自动安装了sql_dialect插件
--2. 自动执行了插件中的sql_dialect_mysql.sql文件,并在__mysql__的schema下有了插件自带函数
--3. 当前会话已可直接使用这些插件函数
alter database xxx set sql_dialect='mysql';

--发送信号,通知其它在线会话优先使用__mysql__的schema下的函数,优先级高于pg_catalog
select pg_reload_conf();

--从pg_extension中可以查询到自动安装的sql_dialect插件
select * from pg_extension where extname='sql_dialect';
      extname      | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition 
-------------------+----------+--------------+----------------+------------+-----------+--------------
 sql_dialect       |       10 |           11 | f              | 1.0.0      |           | 
(1 row)

--从pg_namespace中可以查到自动创建的schema
select * from pg_namespace where nspname='__dialect_mysql__';
      nspname      | nspowner | nsptimeline | nspacl | permspace | usedspace | nsptype 
-------------------+----------+-------------+--------+-----------+-----------+---------
 __dialect_mysql__ |       10 |           0 |        |        -1 |         0 | i
(1 row)

--从pg_proc中可以查到方言插件自带的函数,它们的选取优先级会高于内核,且不必用户指定schema使用
select proname,nspname,prosrc from pg_proc,pg_namespace n where pronamespace=n.oid and nspname='__dialect_mysql__';
    proname    |      nspname      |                   prosrc                    
---------------+-------------------+---------------------------------------------
 rlike         | __dialect_mysql__ | select $1 ~ $2


卸载方言插件

--为当前数据库卸载方言插件
drop extension sql_dialect;


插件升降级

-- 确认当前数据库支持的扩展版本
SELECT * FROM pg_extension;
 
-- 升级扩展到最新版本
ALTER EXTENSION extension_name UPDATE;

-- 升级扩展到指定版本
ALTER EXTENSION extension_name UPDATE TO 'x.y.z';


函数开发指导

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


函数属性介绍

易变性

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

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

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


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


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


不可定义IMMUTABLE的几种情况:

1.函数中引用了表,视图等对象,因为当表的数据发生变化的时候,函数的返回值可能发生变化。
2.函数中引用了STABLE/VOALATILE类型的函数,那么该函数不能定义为IMMUTABLE。
3.函数中有不下推的因素,因为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分布,那么该函数通常不能定义为SHIPPABLE。
2.函数内部有不可下推的因素,函数,语法等,那么该函数不能定义为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。


其它注意事项

数据类型

函数参数的类型和返回值类型,要严格注意。避免类型转换导致的core或结果集错误。


一些常见错误:

应该返回timestamptz,而返回了text。结果看似一样,实际无法使用。
应该返回timestamptz,而返回了timestamp,丢失了时区,未注意到。
不兼容类型的强制转换,导致C函数调用core
调用内存C函数时,需要传入timestamptz类型,却给了timestamp类型,导致时区转换错误。


函数重载

能自动隐式类型转换的类型,尽量只写一个函数。例如:text和timestamp都可以向timestamptz自动转换,不必每个类型都实现一个相似函数。


函数开发

尽量使用pg原生函数定义新函数。因为兼容性函数可能因为各种兼容性导致后期修改,从而级联导致新函数结果集不稳定。


函数变更

函数参数个数和类型,一旦定义,不允许变更。防止用户业务报错。
函数行为也禁止前后不兼容的变更,如果必须要变,需要在内核加GUC参数进行控制。这样也造成了与内核的依赖。


函数开发样例

将新函数放入dialects目录对应的方言文件中,同时还要在升级脚本中再进行处理。


SQL函数

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


PLSql函数

CREATE OR REPLACE FUNCTION func_increment_plsql(i integer) RETURNS integer 
AS $$  
BEGIN
    RETURN i + 1;
END;
$$ LANGUAGE plpgsql;

PL/pgSQL函数的开发,要遵守产品手册中的《PL/pgSQL使用》。


C函数

通过独立C函数(少量使用内核基础函数),都可以放入插件。
在插件中现有或新增的cpp中编写函数,如下:

PG_FUNCTION_INFO_V1(rand_seed);
extern "C" Datum rand_seed(PG_FUNCTION_ARGS);
Datum rand_seed(PG_FUNCTION_ARGS)
{
    int128 n = PG_ARGISNULL(0) ? 0 : PG_GETARG_INT64(0);

    int elevel = ERROR;

    if (unlikely(n > PG_UINT64_MAX)) {
        elog(elevel, "Truncated incorrect DECIMAL value");
        n = PG_UINT64_MAX;
    } else if (unlikely(n < PG_INT64_MIN)) {
        elog(elevel, "Truncated incorrect DECIMAL value");
        n = PG_INT64_MIN;
    }
    gs_srandom((unsigned int)n);
    float8 result;
    /* result [0.0 - 1.0) */
    result = (double)gs_random() / ((double)MAX_RANDOM_VALUE + 1);

    PG_RETURN_FLOAT8(result);
}


在插件的sql文件中注册C函数,如下:

CREATE OR REPLACE FUNCTION __dialect_mysql__.rand_seed(int) 
returns double precision 
LANGUAGE C 
volatile NOT FENCED
as 
'$libdir/libsql_dialect', 'rand_seed';

别忘了在升降级脚本中也要进行处理。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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