摘要:用户在应用数据库过程中,受限于内置函数的性能,局部业务不易实现时,能够应用自定义C函数实现非凡性能。本文通过两个示例展现自定义C函数的实现过程。

前言

用户在应用数据库过程中,经常受限于内置函数的性能,局部业务不易实现,或实现后性能较差,在这些场景呈现时能够思考应用C编写自定义函数来实现独立性能。

例如用户针对某些数据列须要应用C编写的特定算法进行计算,如果放在业务层所性能不能承受,就能够尝试有自定义C函数在实现性能的前提下保障实现效率。

粗略的来说,用户应用C编写的自定义函数会被被编译成动静库并且由数据库在须要的时候载入。在一个会话中第一次调用一个特定的用户定义函数时,数据库过程会把动静库文件载入到内存中以便该函数被调用。从这个角度来说,注册自定义函数时须要筹备编译好的动静库文件和函数定义,以下会以实际操作举例。

数据类型

首先须要先明确,数据库中反对的数据类型在应用自定义C函数操作时,必须将数据库中的数据类型转换为数据库内核能够解决的相干类型,实际上数据库内核在解决内置函数输出时也是相似的操作:

例如比拟常见的

常见的几种类型中,须要留神的是,对text类的函数,C函数在理论解决时往往不是间接应用 gaussdb 外部的 text 类型进行解决的,而是应用C语言的规范类型 char 进行解决的。

这种状况下就能够先读取到入参的地址,再通过gauss提供的内置C函数(比方简略罕用的TextDatumGetCString)转换为 char* 类型字符串进行操作。

利用实例

上面以两个简略的HelloWorld例子来阐明自定义C函数创立的整体流程。

1. 最大公约数

最大公约数的计算比较简单,但外部必然会波及循环,应用SQL实现只能通过相似PL/pgSQL自定义函数或存储过程的办法,如果要达到极限性能的话能够尝试应用自定义C函数实现。

如果以失常的C/C++实现,咱们可能会这样来编写代码

int gcd_c(int a, int b){    int c = a%b;    while(c) {        a = b;        b = c;        c = a%b;    }    return b;}

如果转换成gauss可用的C函数须要进行革新,例如革新成如下文件,并命名未gcd.cpp:

//postgres.h和fmgr.h为gauss中C函数固定宏和根本定义的头文件#include "postgres.h"#include "fmgr.h"//PG_MODULE_MAGIC为固定调用宏,自定义c函数必须在文件开始地位蕴含PG_MODULE_MAGIC;//上面两行也是固定调用,这两行能够指定出一个对外开放,并可在自定义C函数创立时被援用的函数//其中gcd为动静库对外可见接口,前面定义C函数时会用到extern "C" Datum gcd(PG_FUNCTION_ARGS);PG_FUNCTION_INFO_V1(gcd);//理论处理函数int gcd_c(int a, int b){    int c = a%b;    while(c) {        a = b;        b = c;        c = a%b;    }    return b;}//主入口函数//PG_FUNCTION_ARGS是一个固定宏,理论是一个入参出参相干信息的构造体Datumgcd(PG_FUNCTION_ARGS){        //入参阶段    //查看 参数是否为空,如果为空,返回空,相当于对空做查看,避免创立函数时未指定strict属性,导致函数执行异样    if(PG_ARGISNULL(0) || PG_ARGISNULL(1)){        PG_RETURN_NULL();    }    //PG_GETARG_INT32(n)示意从PG_FUNCTION_ARGS构造体中抽取入参的第n个参数,并返回为int32类型    int32        arg1 = PG_GETARG_INT32(0);    int32        arg2 = PG_GETARG_INT32(1);    //调用理论执行函数    int32 res = gcd_c(arg1, arg2);    //返回阶段    PG_RETURN_INT32(res); }

编译动静库,编译时请保障gcc/g++>=5.4

g++ -c -fpic -Wno-write-strings -fstack-protector-all gcd.cpp -I ${GAUSSHOME}/include/postgresql/server/cfunctiong++ -shared -fPIC -Wl,-z,now -o gcd.so gcd.o

执行创立C函数命令:

create or replace function gcd_my(integer,integer)returns integeras 'xxxxx/gcd.so', 'gcd'language c strict not fenced immutable shippable;

这个命令中

  • gcd_my示意前面sql中调用该C函数时应用的名字
  • xxxxxx/gcd.so示意的是以后环境上编译生成动静库搁置的地位
  • language c示意该自定义函数为C语言编写的函数
  • not fenced示意该函数执行时应用的是非fenced模式(该模式前面大节会再次阐明)
  • strict示意输出不能为空,否则返回也为空(上局部C函数尽管以对空值做了解决,以后为了阐明问题,此处也申明了strict属性)
  • 其余部分为create function的公共内容,具体能够参考手册中create function章节

执行函数

select gcd_my(12, 16);

2. 将字符串中的第一个字母大写,其余小写

内置的字符串处理函数中有大小写转换函数,但没有这种相似只有第一个字符进行大小写转换的定制化函数,如此,咱们就能够尝试应用自定义C函数进行实现。
如果以失常的C/C++语言操作,咱们可能会这样来写

#include <string.h>void upper_str(char* str){    bool has_first = false;    for(int i = 0; i< strlen(str); i++){        if((str[i]>='a' && str[i]<='z') || (str[i]>='a' && str[i]<='z')) {            if(has_first) {                str[i]=str[i]%0x20 + 'a';            } else {                str[i]=str[i]%0x20 + 'A';            }        }    }}

如果转换成gauss可用的C函数能够革新成如下文件,并命名未upper_str.cpp:

#include "postgres.h"#include "fmgr.h"//builtins.h中存在上面应用的TextDatumGetCString的定义#include "utils/builtins.h"#include <string.h>PG_MODULE_MAGIC;extern "C" Datum upper_str(PG_FUNCTION_ARGS);PG_FUNCTION_INFO_V1(upper_str);//理论处理函数void upper_str_c(char* str){    bool has_first = false;    for(int i = 0; i< strlen(str); i++){        if((str[i]>='A' && str[i]<='Z') || (str[i]>='a' && str[i]<='z')) {            if(has_first) {                str[i]=str[i]%0x20 + 'a' - 1;            } else {                str[i]=str[i]%0x20 + 'A' - 1;                has_first=true;            }        }    }}//主入口函数Datumupper_str(PG_FUNCTION_ARGS){        //入参阶段    if(PG_ARGISNULL(0)){        PG_RETURN_NULL();    }    Datum source = PG_GETARG_DATUM(0);    char *src = TextDatumGetCString(source);     //理论调用函数    (void) upper_str_c(src);     //返回阶段    //cstring_to_text能够将char*类型转换为gauss内置的text*类型    //PG_RETURN_TEXT_P宏能够返回text*类型的构造,被下层调用获取    PG_RETURN_TEXT_P(cstring_to_text(src)); }

执行创立C函数命令:

create or replace function upper_str_my(text)returns textas 'xxxxxx/upper_str.so', 'upper_str'language c strict not fenced immutable shippable;

执行函数

select upper_str_my('@hello World');

注意事项

  1. fenced/not fenced 模式

C函数在注册时有一个选项时fenced,这个模式是gauss提供的一种过程隔离机制。如果在fenced模式下理论执行的函数会放在一个独自启动的过程中执行,而not fenced模式则是在理论执行时和gaussdb同一过程。两种模式各有优劣,须要依据理论状况进行抉择。比拟倡议的形式是,受限创立为fenced模式函数,调试无问题后,再从新注册为not fenced模式,以高效率模式运行。

  • fenced模式
    长处:执行更平安,如果函数执行出现异常,不会影响到CN/DN过程,保障整个节点的稳固运行;
    毛病:须要额定的过程开销,效率较低。
  • not fenced模式正好相同
    长处:运行效率高,无额定开销;
    毛病:如果自定义C代码编码有问题,容易造成CN/DN过程异样等重大问题。
  1. C函数编写时,须要留神注册时的参数类型和返回类型,零碎会依据创立函数时指定的内容传给理论执行的函数,所以如果函数外部解决和创立时指定类型不统一容易呈现不可预测的异样
  2. C函数编写时,须要留神对空值进行非凡解决,或者再创立函数时指定为strict属性的函数
  3. C函数实现时都是底层实现,应该严格控制不牢靠C函数的创立,保持谨慎应用自定义C函数的准则
  4. C函数创立只能由具备sysadmin权限的用户进行创立,能够通过grant操作赋予其余用户执行权限

总结

自定义C函数的存在给用户提供了间接实现底层逻辑的机会,一个实现欠缺的自定义C函数,往往能够给业务带来极强的定制性和大幅度的性能晋升。但定制性也是一把双刃剑,如果编写自定义C函数时做的不够欠缺存在逻辑问题,甚至内存溢出等重大问题,也极可能使零碎解体。因而能够应用自定义C函数作为一个强有力的工具为理论业务增光添彩,但也须要审慎的创立应用任意一个C函数防止误伤其余业务。

本文分享自华为云社区《初窥自定义C函数》,原文作者:sincatter 。

点击关注,第一工夫理解华为云陈腐技术~