• [技术干货] 内存自适应技术
    针对静态内存管理机制的弊端,GaussDB(DWS)设计实现了内存自适应控制技术,主要目的如下: GaussDB(DWS)技术原理-优化器 1) 去除静态内存管理对work_mem的依赖。可以由SQL引擎优化器模块自动估算每个算子所需的内存; 2) 避免大并发场景下内存不足现象的发生。资源管理模块根据SQL引擎优化器对于每个查询内存的估算值,对每个查询进行调度,如果超过系统可用内存,则进行排队。 动态资源管理与内存自适应技术的组件图如上图所示。我们从多个CN中选择一个CN,命名为 CCN(Central CN),进行语句队列的管理。对于每个查询SQL,CN在生成完执行计划后,为每个物化算子分配合适的内存,同时计算整个语句内存使用量,并将语句及对应的内存使用量发给CCN。CCN维护系统可用的内存值,对于新来的语句,如果语句内存使用量小于可用内存值,则允许其下发到DN执行,否则挂起,等到有语句结束释放内存后再次将其唤醒,是否可以下发。
  • [技术干货] 下盘机制
    GaussDB(DWS)也提供下盘的机制,当上述操作符需要使用的内存太大时,可以将部分或全部的数据下盘处理,提高内存的使用效率,但相应的查询性能也会受到影响。PG 使用 work_mem 参数来控制算子可使用内存的阈值,当使用内存超过阈值时,就需要做下盘处理。GaussDB(DWS)的静态内存管理机制也延续了 PG 的处理机制,使用work_mem来控制单算子的内存使用上限。 静态内存管理存在较大弊端,需要调优人员能够根据数据量、语句复杂程度和系统的内存大小设置合理的work_mem,既避免 work_mem设置太大导致系统资源不够用,还要考虑到数据规模,保证大部分算子不下盘。通常情况下,这个是很难做到的,有以下几点原因: 1) 通常情况下,复杂语句的执行计划中包含多个复杂算子,每个算子的内存使用上限是work_mem,我们没有办法计算一个语句要使用多少内存,因此也就不容易设置一个最优的work_mem参数,保证尽可能不下盘,同时内存又够用。并发场景更无法设置了; 2) work_mem只是每个算子内存使用的上限,并不是预分配;如果数据量没有那么大的话,实际内存使用是达不到work_mem的。因此也会影响work_mem的设置; 3) 每个语句的场景不一样,有的语句包含多个物化算子,而另外的语句只有一个物化算子,而这个算子对内存的需求会比较大,因此无法全局统一地进行设置。 
  • [技术干货] 静态内存管理机制及限制
    GaussDB(DWS)的执行引擎继承自PG,对于优化器生成的执行计划树,总体采取执行算子+流水线的处理方式。对于NestLoop 算子节点,需要首先从左树的IndexScan算子节点获取元组,然后到右子树的IndexScan算子节点进行连接,匹配元组后进行输出。流水线的执行方式使得对于NestLoop, IndexScan类的一般算子,同时只有一定数量的元组处于内存中,对于行引擎每个算子仅占用一条元组的空间,对于列引擎占用一个batch(最多1000条元组)的空间,占用的空间较小,基本可以忽略不计。但是,GaussDB(DWS)中也有一些需要将所有数据收集后进行处理的算子,在执行时需要使用较多的内存,通常我们称这类算子为物化算子。GaussDB(DWS)中主要存在如下不同种类的物化算子: 1) HashJoin:Hash连接操作符,主要思想是计算左右两表连接列的hash值,通过hash值比较减少元组比较的次数,需要将一个表建立hash表,另一个表进行hash值比较操作,建立hash表需要在内存中进行; 2) HashAgg:Hash聚集操作符,主要思想同HashJoin类似,通过hash值比较减少元组去重比较的次数,需要将不同值的元组保存的内存中; 3) Sort:排序操作符,需要获取所有元组后进行排序操作,待排序元组均存在于内存中; 4) Materialize:物化操作符,通常在需要重复扫描时使用,通过将结果存储在内存中,保证重复扫描时的效率。 
  • [技术干货] Aggregate Path 的生成
    一般而言,Aggregate Path 生成是在表关联的 Path 生成之后,且有三个主要步骤(Unique Path的Aggregate在Join Path生成的时候就已经完成了,但也会有这三个步骤):先估算出聚集结果的行数,然后选择Path的方式,最后创建出最优Aggregate Path。前者依赖于统计信息和 Cost 估算模型,后者取决于前者的估算结果、集群规模和系统资源。Aggregate 行数估算主要根据聚集列的 distinct 值来组合,我们重点关注Aggregate 行数估算和最优Aggregate Path选择。统计信息显示t1.c2和t2.c2的原始distinct值分别是-0.25和100,-0.25转换为绝对值就是0.25 * 2000 = 500,那它们的组合distinct是不是至少应该是500呢?答案不是。因为Aggregate对JoinRel(t1, t2)的结果进行聚集,而系统表中统计信息是原始信息(没有任何过滤)。这时需要把Join条件和过滤条件都考虑进去,如何考虑呢?首先看过滤条件 “t1.c1<500“可能会过滤掉一部分t1.c2,那么就会有个选择率(此时我们称之为 FilterRatio),然后 Join 条件"t1.c2 = t2.c2"也会有一个选择率(此时我们称之为JoinRatio),这两个 Ratio 都是介于[0, 1]之间的一个数,于是估算t1.c2的distinct时这两个Ratio影响都要考虑。如果不同列之间选择Poisson模型,相同列之间用完全相关模型。
  • [技术干货] Join Path的生成
    1) Join 路径的选择时,会分两个阶段计算代价,initial和final 代价,initial 代价快速估算了建hash表 、计 算 hash值以及下盘的代价,当initial代价已经比path_list中某个path大时,就提前剪枝掉该路径; 2) cheapest_total_path 有多个原因:主要是考虑到多个维度下,代价很相近的路径都有可能是下一层动态规划的最佳选择,只留一个可能得不到整体最优计划; 3) cheapest_startup_path 记录了启动代价最小的一个,这也是预留了另一个维度,当查询语句需要的结果很少时,有一个启动代价很小的 Path,但总代价可能比较大,这个Path有可能会成为首选; 4) 由于剪枝的原因,有些情况下,可能会提前剪枝掉某个Path,或者这个Path没有被选为cheapest_total_path或cheapest_startup_path,而这个 Path是理论上最优计划的一部分,这样会导致最终的计划不是最优的,这种场景一般概率不大,如果遇到这种情况,可尝试使用Plan Hint进行调优; 5) 路径生成与集群规模大小、系统资源、统计信息、Cost 估算都有紧密关系,如集群DN数影响着重分布的倾斜性和单DN的数据量,系统内存影响下盘代价,统计信息是行数和distinct值估算的第一手数据,而Cost估算模型在整个计划生成中,是选择和淘汰的关键因素,每个JoinRel的行数估算不准,都有可能影响着最终计划。因此,相同的SQL语句,在不同集群或者同样的集群不同统计信息,计划都有可能不一样,如果路径发生一些变化可通过分析Performance信息和日志来定位问题,Performance 详解可以参考博文:GaussDB(DWS)的 explain performance 详解; 6) 如果设置了Random Plan模式,则动态规划的每一层cheapest_startup_path和cheapest_total_path 都是从 path_list 中随机选取的,这样保证随机性。
  • [技术干货] 最优路径
    每一种路径又可以搭配不同的Join方 法( NestLoop、HashJoin、MergeJoin),总计18种关联路径,优化器需要在这些路径中选择最优路径,筛选的依据就是路径的代价(Cost)。优化器会给每个算子赋予代价,比如 Seq Scan,Redistribute,HashJoin都有代价,代价与数据规模、数据特征、系统资源等等都有关系,关于代价如何估算,由于代价与执行时间成正比,优化器的目标是选择代价最小的计划,因此路径选择也是一样。路径代价的比较思路大致是这样,对于产生的一个新Path,逐个比较该新Path与path_list中的path,若 total_cost很相近,则比较startup cost,如果也差不多,则保留该 Path 到 path_list 中去;如果新路径的total_cost 比较大,但是startup_cost小很多,则保留该Path,此处略去具体的比较过程,直接给出Path的比较结果。t1 和t3表的关联条件是:t1.c1 = t3.c2,因为t1的Join列是分布键c1列,于是t1表上不需要加Redistribute;由 于 t1和t3的Join方式是Semi Join,外表不能Broadcast,否者可能会产生重复结果;另外还有一类Unique Path选择(即t3表去重)。由于只有一边需要重分布且可以进行重分布,则不选Broadcast,因为相同数据量时Broadcast 的代价一般要高于重分布,提前剪枝掉。再把Join方法考虑进去,于是优化器给出了最终选择。此时的最优计划是选择了内表Unique Path的路径,即t3表先去重,然后在走Inner Join 过程。 
  • [技术干货] 路径生成
    已知表关联的方式有多种(比如 NestLoop、HashJoin)、且GaussDB(DWS)的表是分布式的存储在集群中,那么两个表的关联方式可能就有多种了,而我们的目标就是,从这些给定的基表出发,按要求经过一些操作(过滤条件、关联方式和条件、聚集等等),相互组合,层层递进,最后得到我们想要的结果。这就好比从基表出发,寻求一条最佳路径,使得我们能最快得到结果,这就是我们的目的。GaussDB(DWS)优化器选择的基本思路是动态规划,顾名思义,从某个开始状态,通过求解中间状态最优解,逐步往前演进,最后得到全局的最优计划。那么在动态规划中,总有一个变量,驱动着过程演进。在这里,这个变量就是表的个数。我们先记住这三组Path 名称:path_list,cheapest_startup_path ,cheapest_total_path,后面两个就对应了动态规划的局部最优解,在这里是一组集合,统称为最优路径,也是下一步的搜索空间。path_list里面存放了当前Rel集合上的有价值的一组候选 Path(被剪枝调的 Path 不会放在这里),cheapest_startup_path 代表path_list 中启动代价最小的那个Path,cheapest_total_path代表path_list里一组总代价最小的Path(这里用一组主要是可能存在多个维度分别对应的最优Path)。t2表和t3 表类似,最优路径都是一条Seq Scan。有了所有基表的Scan最优路径,下面就可以选择关联路径了。
  • [技术干货] 多组Join条件估算思想
    表关联含有多个Join条件时,与基表过滤条件估算类似,也有两种思路,优先尝试多列统计信息进行选择率估算。当无法使用多列统计信息时,则使用单列统计信息按照上述方法分别计算出每个Join条件的选择率。那么组合选择率的方式也由参数cost_param控制。以下是特殊情况的选择率估算方式: 如果Join列是表达式,没有统计信息的话,则优化器会尝试估算出distinct值,然后按没有MCV的方式来进行估算; Left Join/Right Join 需特殊考虑以下一边补空另一边全输出的特点,以上模型进行适当的修改即可; 如果关联条件是范围类的比较,比如"t1.c2 < t2.c2",则目前给默认选择率:1 / 3。两表关联时,如果基表上有一些无法下推的过滤条件,则一般会变成JoinFilter,即这些条件是在Join过程中进行过滤的,因此JoinFilter会影响到JoinRel的行数,但不会影响基表扫描上来的行数。严格来说,如果把 JoinRel 看成一个中间表的话,那么这些JoinFilter 是这个中间表的过滤条件,但JoinRel还没有产生,也没有行数和统计信息,因此无法准确估算。然而一种简单近似的方法是,仍然利用基表,粗略估算出这个JoinFilter的选择率,然后放到JoinRel最终行数估算中去。
  • [技术干货] JoinRel 行数估算
    基表行数估算完,就可以进入表关联阶段的处理了。那么要关联两个表,就需要一些信息,如基表行数、关联之后的行数、关联的方式选择(也叫Path的选择,请看下一节),然后在这些方式中选择代价最小的,也称之为最佳路径。对于关联条件的估算,也有单个条件和多个条件之分,优化器需要算出所有Join条件和JoinFilter的综合选择率,然后给出估算行数,先看单个关联条件的选择率如何估算。t1.c2 列没有 MCV 值,平均每个 distinct 值大约重复 4 次且是均匀分布,由于Histogram 中保留的数据只是桶的边界,并不是实际有哪些数据(重复收集统计信息,这些边界可能会有变化),那么实际拿边界值来与t2.c2进行比较不太实际,可能会产生比较大的误差。此时我们坚信一点:“能关联的列与列是有相同含义的,且数据是尽可能有重叠的”,也就是说,如果t1.c2列有500个distinct值,t2.c2列有100个distinct值,那么这100个与500个会重叠100个,即distinct值小的会全部在distinct值大的那个表中出现。虽然这样的假设有些苛刻,但很多时候与实际情况是较吻合的。回到本例,根据统计信息,n_distinct 显示负值代表占比,而t1表的估算行数是2000因为基表t1上还有个过滤条件"t1.c1 > 100",当前关联是发生在基表过滤条件之后的,估算的distinct 应该是过滤条件之后的 distinct 有多少,不应是原始表上有多少。那么此时可以采用各种假设模型来进行估算,比如几个简单模型:Poisson 模型(假设 t1.c1 与t1.c2 相关性很弱)或完全相关模型(假设t1.c1与t1.c2 完全相关),不同模型得到的值会有差异。
  • [技术干货] 多列过滤条件估算思想
    仅有单列统计信息 该情况下,首先按单列统计信息计算每个过滤条件的选择率,然后选择一种方式来组合这些选择率,选择的方式可通过设置cost_param来指定。为何需要选择组合方式呢?因为实际模型中,列与列之间是有一定相关性的,有的场景中相关性比较强,有的场景则比较弱,相关性的强弱决定了最后的行数。 有多列组合统计信息 如果过滤的组合列的组合统计信息已经收集,则优化器会优先使用组合统计信息来估算行数,估算的基本思想与单列一致,即将多列组合形式上看成“单列”,然后再拿多列的统计信息来估算。 比如,多列统计信息有:((c1, c2, c4)),((c1, c2)),双括号表示一组多列统计信息:若条件是:c1 = 7 and c2 = 3 and c4 = 5,则使用((c1, c2, c4)); 若条件是:c1 = 7 and c2 = 3,则使用((c1, c2)); 若条件是:c1 = 7 and c2 = 3 and c5 = 6,则使用((c1, c2)); 多列条件匹配多列统计信息的总体原则是:多列统计信息的列组合需要被过滤条件的列组合包含; 所有满足“条件1”的多列统计信息中,选取“与过滤条件的列组合的交集最大“的那个多列统计信息。 对于无法匹配多列统计信息列的过滤条件,则使用单列统计信息进行估算。目前使用多列统计信息时,不支持范围类条件;如果有多组多列条件,则每组多列条件的选择率相乘作为整体的选择率; 上面说的单列条件估算和多列条件估算,适用范围是每个过滤条件中仅有表的一列,如果一个过滤条件是多列的组合,比如 “t1.c1 < t1.c2”,那么一般而言单列统计信息是无法估算的,因为单列统计信息是相互独立的,无法确定两个独立的统计数据是否来自一行。目前多列统计信息机制也不支持基表上的过滤条件涉及多列的场景; 无法下推到基表的过滤条件,则不纳入基表行数估算的考虑范畴,如上述:t1.c3 is not null or t2.c3 is not null,该条件一般称为JoinFilter,会在创建JoinRel时进行估算; 如果没有统计信息可用,那就给默认选择率了。 
  • [技术干货] 优化器的计划生成方法
    GaussDB(DWS)优化器的计划生成方法有两种,一是动态规划,二是遗传算法,前者是使用最多的方法,也是本系列文章重点介绍对象。一般来说,一条 SQL 语句经语法树(ParseTree)生成特定结构的查询树(QueryTree)后,从QueryTree开始,才进入计划生成的核心部分,其中有一些关键步骤: 1) 设置初始并行度(Dop); 2) 查询重写; 3) 估算基表行数; 4) 估算关联表(JoinRel); 5) 路径生成,生成最优Path; 6) 由最优Path创建用于执行的Plan节点; 7) 调整最优并行度。基表行数估算目前主要依赖于统计信息,统计信息是先于计划生成由Analyze触发收集的关于表的样本数据的一些统计平均信息,如t1表的部分统计信息如下: null_frac:空值比例 n_distinct:全局 distinct 值,取值规则:正数时代表distinct值,负数时其绝对值代表distinct 值与行数的比 n_dndistinct:DN1上的distinct值,取值规则与n_distinct类似 avg_width:该字段的平均宽度 GaussDB(DWS)技术原理-优化器 most_common_vals:高频值列表 most_common_freqs:高频值的占比列表,与most_common_vals对应 从上面的统计信息可大致判断出具体的数据分布,如t1.c1列,平均宽度是4,每个数据的平均重复度是 2,且没有空值,也没有哪个值占比明显高于其他值,即most_common_vals(简称MCV)为空,这个也可以理解为数据基本分布均匀,对于这些分布均匀的数据,则分配一定量的桶,按等高方式划分了这些数据,并记录了每个桶的边界,俗称直方图(Histogram),即每个桶中有等量的数据。 
  • [技术解读] GaussDB Hibernate框架插入数据开启校验时报错
    GaussDB Hibernate框架插入数据开启校验时报错问题现象客户从A模式数据库迁移到GaussDB数据库,表结构使用DRS工具进行迁移,迁移后客户原业务代码不可用,Hibernate框架校验表结构报错。Schema-validation: wrong column type encountered in column [execute_time] in table [abnormal_event]; found [int8 (Types#BIGINT)], but expecting [int4 (Types#INTEGER)]示例原A模式数据库中Student表结构为如下格式:id number(15,0) sid number(15,0) age number(10,0) 使用DRS工具进行迁移GaussDB数据库后Student表结构为如下格式:id bigint sid bigint age Integer当实际数据库中Student实体类信息如下时:Long id Long sidLong age --Hibernate框架校验表结构报错。hibernate.cfg.xml配置文件:<hibernate-configuration> <session-factory> <!--GaussDB连接信息--> <property name="connection.driver_class">com.huawei.gaussdb.jdbc.Driver</property> <property name="connection.url">jdbc:gaussdb://x.x.x.x:xx/postgres?currentSchema=public</property> <property name="connection.username">xxxxxx</property> <property name="connection.password">xxxxxx</property> <!--以下为可选配置--> <!--是否支持方言--> <!--在pgsql兼容模式下,必须有如下配置--> <property name="dialect">org.hibernate.dialect.PostgreSQL92Dialect</property> <!--在A数据库兼容模式下,推荐如下配置--> <!-- <property name="dialect">org.hibernate.dialect.OracleDialect</property>--> <property name="met"></property> <!--执行CURD时是否打印SQL语句 --> <property name="show_sql">true</property> <!--自动建表--> <!-- <property name="hbm2ddl.auto">create</property>--> <!--插入数据时开启校验--> <property name="hbm2ddl.auto">validate</property> <!--关闭校验--> <!-- <property name="hbm2ddl.auto">none</property>--> <!-- 资源注册(实体类映射文件)--> <mapping resource="student.xml"/> </session-factory> </hibernate-configuration>插入数据的主函数:// 创建要测试的对象。Student student = new Student(); student.setId(20L); student.setAge(222L); student.setSid(222L); // 开启事务,基于session得到对象。Configuration conf = new Configuration().configure(); SessionFactory sessionFactory = conf.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); // 通过session保存数据。 session.save(student); // 提交事务。 transaction.commit(); // 操作完毕,关闭session连接对象。 session.close(); 原因分析A模式数据库能够插入成功并通过校验:Hibernate框架在校验数据时,通过判断插入数据类型java.lang.long的TypeCode,与表结构的number类型的TypeCode进行对比,结果不一致,随后会把插入数据的long类型的sqlType与表结构的Type作比较,将long类型转成number(19,0)(对应关系由org.hibernate.dialect.OracleDialect进行维护),因为表结构的type是number类型,number(19,0)以number开头,所以校验可以通过。使用A模式数据库时,业务代码涉及整数类型的数据,可以使用Java中的Long类型插入。GaussDB数据库校验不同,关闭校验后数据插入成功:Hibernate框架在插入数据做校验时,业务代码是java.lang.long类型,和表数据类型bigint的TypeCode是对应的,因此不报错。但是表数据类型是Integer时无法对应,首先判断业务代码java.lang.long类型的TypeCode,与表数据Integer的TypeCode比较,结果不一致,再根据org.hibernate.dialect.PostgreSQLDialect对应关系,会将Long类型转换为int8,将表数据类型Integer转换为int4,无法对应,所以校验失败。使用GaussDB数据库时,业务代码涉及整数类型的数据,必须使用表类型对应的Java整型。处理方法可使用以下方式进行问题处理:关闭校验功能。客户修改业务代码,针对不同的表数据类型,采用对应的Java整型。
  • [技术解读] GaussDB 数据库连接技术全解析:从基础连接到高性能集群
    GaussDB 数据库连接技术全解析:从基础连接到高性能集群引言在金融交易、物联网等高并发场景中,GaussDB作为分布式数据库需处理百万级连接请求。本文将深入解析多种连接方式的技术细节,涵盖标准SQL接口、JDBC/ODBC驱动、私有协议连接等,提供从基础连接到性能优化的完整解决方案。一、核心连接方式对比连接方式 协议类型 适用场景 典型延迟标准SQL PostgreSQL协议 通用开发场景 5-20msJDBC Java专用协议 企业级Java应用 8-30msODBC 标准ODBC协议 跨平台C/C++应用 10-40ms高性能私有协议 GaussDB私有协议 金融核心交易系统 2-8ms二、典型连接实现方案2.1 标准SQL客户端连接bash# 使用psql命令行工具连接 psql "host=node1 port=6030 dbname=finance_db user=admin password=Secure@2023# sslmode=require" 连接参数详解:​texthost: 数据库节点地址(支持多节点逗号分隔)port: 监听端口(默认6030)dbname: 数据库名称user: 用户名password: 密码sslmode: 加密等级(require/verify-full等)connect_timeout: 连接超时秒数(建议≥5)2.2 Java应用JDBC连接2.2.1 基础连接示例javaimport java.sql.*; public class GaussDBDemo { public static void main(String[] args) { String url = "jdbc:gaussdb://node1:6030/finance_db?" + "user=admin&password=Secure@2023#" + "ssl=true&sslmode=verify-full"; try (Connection conn = DriverManager.getConnection(url)) { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT NOW()"); while (rs.next()) { System.out.println(rs.getString(1)); } } catch (SQLException e) { e.printStackTrace(); } } } 2.2.2 连接池高级配置(HikariCP)properties# HikariCP配置示例 spring.datasource.hikari.maximum-pool-size=200 spring.datasource.hikari.minimum-idle=50 spring.datasource.hikari.idle-timeout=30000 spring.datasource.hikari.connection-timeout=5000 spring.datasource.hikari.leak-detection-threshold=600002.3 Python连接实现2.3.1 psycopg2基础连接pythonimport psycopg2 from psycopg2 import pool # 创建连接池 pg_pool = psycopg2.pool.SimpleConnectionPool( minconn=5, maxconn=20, host="node1,node2", port=6030, database="finance_db", user="admin", password="Secure@2023#", sslmode="require" ) # 获取连接 conn = pg_pool.getconn() cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM transactions") print(cursor.fetchone()[0]) cursor.close() pg_pool.putconn(conn) 2.3.2 异步连接(asyncpg)pythonimport asyncio import asyncpg async def main(): conn = await asyncpg.connect( user='admin', password='Secure@2023#', database='finance_db', host='node1', port=6030, ssl='require' ) values = await conn.fetch('SELECT * FROM transactions WHERE amount > $1', 1000) await conn.close() asyncio.run(main()) 三、高性能集群连接方案3.1 多节点负载均衡配置java// Java动态节点选择示例 String[] nodes = {"node1:6030", "node2:6030", "node3:6030"}; Random random = new Random(); String selectedNode = nodes[random.nextInt(nodes.length)]; Properties props = new Properties(); props.setProperty("user", "admin"); props.setProperty("password", "Secure@2023#"); props.setProperty("ssl", "true"); props.setProperty("targetServerType", "primary"); // 强制主节点 Connection conn = DriverManager.getConnection( "jdbc:gaussdb://" + selectedNode + "/finance_db", props ); 3.2 连接参数调优指南参数名称 推荐值 作用说明login_timeout 10 连接超时秒数keepalives_idle 60 空闲心跳间隔(秒)keepalives_interval 10 心跳包发送间隔(秒)target_session_attrs read-write 会话属性要求load_balance_host on 启用主机负载均衡四、安全增强连接方案4.1 SSL加密配置(双向认证)bash# 生成客户端证书 openssl req -new -key client-key.pem -out client.csr openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt # JDBC双向认证配置 String url = "jdbc:gaussdb://node1:6030/finance_db?" + "ssl=true&sslmode=verify-full" + "&sslcert=/path/to/client.crt" + "&sslkey=/path/to/client.key"; 4.2 IP白名单控制sql-- 创建访问控制策略 CREATE ACCESS POLICY ip_whitelist_policy FOR CONNECT USING (client_addr IN ('192.168.1.0/24', '10.0.0.0/16')) WITH CHECK OPTION; 五、连接监控与故障排查5.1 实时连接状态查看sql-- 查看当前活跃连接 SELECT usename, application_name, client_addr, state, backend_start, query FROM pg_stat_activity WHERE datname = 'finance_db'; 5.2 典型故障诊断流程场景:连接超时(错误代码:08001)​text检查网络连通性:telnet node1 6030验证防火墙规则:sudo iptables -L -n查看数据库日志:tail -f /var/log/gaussdb/server.log确认连接参数正确性:特别是SSL相关配置测试最小化连接参数:仅保留host/port/user/password六、未来演进方向​智能连接路由:基于AI的实时负载预测与连接分配​量子安全通信:量子密钥分发(QKD)加密通道​服务网格集成:通过Service Mesh实现细粒度流量管控​无感知重连机制:基于HTTP/3的自动故障转移协议结论通过本文的系统解析,开发者可掌握GaussDB全场景连接技术。关键实践包括:根据场景选择最优连接协议(标准SQL/JDBC/私有协议)通过连接池和负载均衡提升吞吐量实施多层次安全防护机制建立完善的监控与故障排查体系建议结合华为云提供的连接性能调优工具进行深度优化,并定期演练故障恢复流程。随着GaussDB生态的持续完善,开发者应关注新特性如基于RDMA的高速网络协议、AI驱动的连接预测等技术创新。
  • [技术解读] GaussDB游标管理
    使用游标可以检索出多行的结果集,应用程序必须声明一个游标并且从游标中抓取每一行数据。声明一个游标:EXEC SQL DECLARE c CURSOR FOR select * from tb1; 打开游标:EXEC SQL OPEN c; 从游标中抓取一行数据:EXEC SQL FETCH 1 in c into :a, :str; 关闭游标:EXEC SQL CLOSE c; 说明:在开启GUC参数enable_ecpg_cursor_duplicate_operation(默认开启)的情况下,使用ECPG连接ORA兼容数据库时,允许通过如下方式重复打开/关闭游标:EXEC SQL OPEN c; EXEC SQL OPEN c; EXEC SQL CLOSE c; EXEC SQL CLOSE c; 更多游标的使用细节请参见DECLARE,关于FETCH命令的细节请参见FETCH。完整使用示例:#include <string.h> #include <stdlib.h> int main(void) { exec sql begin declare section; int *a = NULL; char *str = NULL; exec sql end declare section; int count = 0; /* 提前创建testdb */ exec sql connect to testdb ; exec sql set autocommit to off; exec sql begin; exec sql drop table if exists tb1; exec sql create table tb1(id int, info text); exec sql insert into tb1 (id, info) select generate_series(1, 100000), 'test'; exec sql select count(*) into :a from tb1; printf ("a is %d\n", *a); exec sql commit; // 定义游标 exec sql declare c cursor for select * from tb1; // 打开游标 exec sql open c; exec sql whenever not found do break; while(1) { // 抓取数据 exec sql fetch 1 in c into :a, :str; count++; if (count == 100000) { printf("Fetch res: a is %d, str is %s", *a, str); } } // 关闭游标 exec sql close c; exec sql set autocommit to on; exec sql drop table tb1; exec sql disconnect; ECPGfree_auto_mem(); return 0; } WHERE CURRENT OF cursor_namecursor_name:指定游标的名称。当cursor指向表的某一行时,可以使用此语法更新或删除cursor当前指向的行。使用限制及约束请参考UPDATE章节对此语法介绍。完整使用示例:#include <string.h> #include <stdlib.h> int main(void) { exec sql begin declare section; int va; int vb; exec sql end declare section; int count = 0; /* 提前创建好testdb */ exec sql connect to testdb ; exec sql set autocommit to off; exec sql begin; exec sql drop table if exists t1; exec sql create table t1(c1 int, c2 int); exec sql insert into t1 values(generate_series(1,10000), generate_series(1,10000)); exec sql commit; exec sql declare cur1 cursor for select * from t1 where c1 < 100 for update; /* 打开游标 */ exec sql open cur1; exec sql fetch 1 in cur1 into :va, :vb; printf("c1:%d, c2:%d\n", va, vb); /* 使用where current of删除当前行 */ exec sql delete t1 where current of cur1; exec sql fetch 1 in cur1 into :va, :vb; exec sql fetch 1 in cur1 into :va, :vb; printf("c1:%d, c2:%d\n", va, vb); /* 使用where current of更新当前行 */ exec sql update t1 set c2 = 21 where current of cur1; exec sql select c2 into :vb from t1 where c1 = :va; printf("c1:%d, c2:%d\n", va, vb); /* 关闭游标 */ exec sql close cur1; exec sql set autocommit to on; exec sql drop table t1; exec sql disconnect; ECPGfree_auto_mem(); return 0; }
  • [技术解读] GaussDB回调机制深度实践:从事件驱动到系统集成
    GaussDB回调机制深度实践:从事件驱动到系统集成核心实现技术栈触发器回调开发sql-- 创建审计触发器回调 CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$ BEGIN INSERT INTO audit_log ( operation, table_name, user_name, exec_time ) VALUES ( TG_OP, TG_TABLE_NAME, current_user, current_timestamp ); RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER audit_dml_trigger AFTER INSERT OR UPDATE OR DELETE ON orders FOR EACH ROW EXECUTE FUNCTION audit_trigger(); 事件通知回调sql-- 使用LISTEN/NOTIFY实现异步回调 LISTEN order_created; -- 发送通知 NOTIFY order_created, json_build_object( 'order_id', NEW.id, 'amount', NEW.amount )::text; 外部程序回调python# Python回调处理器示例 import psycopg2 import requests def db_callback(event): if event['type'] == 'order_created': payload = { 'order_id': event['data']['order_id'], 'callback_url': 'https://api.example.com/order' } response = requests.post( payload['callback_url'], json=payload, timeout=5 ) return response.json() def listen_for_events(): conn = psycopg2.connect(...) conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) cur = conn.cursor() cur.execute("LISTEN order_created;") while True: conn.poll() while conn.notifies: notify = conn.notifies.pop(0) result = db_callback(json.loads(notify.payload)) print(f"Callback result: {result}") 高级应用场景实现双向回调系统集成mermaidsequenceDiagram participant App participant GaussDB participant ExternalService App->>GaussDB: 订阅order_created事件 GaussDB-->>App: 返回订阅确认 loop 事件发生 GaussDB->>App: 发送NOTIFY消息 App->>ExternalService: 调用REST API ExternalService-->>App: 返回处理结果 App->>GaussDB: 更新处理状态 end动态回调路由配置sql-- 创建回调路由表 CREATE TABLE callback_router ( event_type TEXT PRIMARY KEY, handler_function TEXT, retry_policy JSONB ); -- 动态调用处理器 DO $$ DECLARE router RECORD; BEGIN SELECT * INTO router FROM callback_router WHERE event_type = TG_EVENT; EXECUTE format('SELECT %I(%L)', router.handler_function, row_to_json(NEW)); END; $$ LANGUAGE plpgsql; 性能优化关键技术异步回调队列管理sql-- 使用内存队列提升吞吐量 CREATE EXTENSION pg_cron; -- 批量处理回调任务 CREATE OR REPLACE FUNCTION process_callbacks() RETURNS VOID AS $$ BEGIN PERFORM dblink_exec( 'dbname=gaussdb user=admin', 'COPY (SELECT * FROM callback_queue) TO PROGRAM ''curl -X POST ...''' ); DELETE FROM callback_queue WHERE processed_at IS NOT NULL; END; $$ LANGUAGE plpgsql; -- 设置定时任务 SELECT cron.schedule('*/1 * * * *', $$SELECT process_callbacks()$$); 回调限流策略sql-- 使用令牌桶算法控制速率 CREATE TABLE callback_limits ( bucket_id TEXT PRIMARY KEY, tokens INTEGER DEFAULT 100, last_refill TIMESTAMP ); -- 限流装饰器 CREATE OR REPLACE FUNCTION rate_limited_callback() RETURNS TRIGGER AS $$ BEGIN PERFORM refill_tokens(); IF (SELECT tokens FROM callback_limits WHERE bucket_id = 'default') > 0 THEN UPDATE callback_limits SET tokens = tokens - 1; RETURN NEW; ELSE RAISE NOTICE 'Rate limit exceeded'; RETURN NULL; END IF; END; $$ LANGUAGE plpgsql; 安全防护体系回调验证机制sql-- 数字签名验证 CREATE OR REPLACE FUNCTION verify_signature( payload JSONB, signature TEXT ) RETURNS BOOLEAN AS $$ DECLARE secret_key TEXT := 'your-secret-key'; BEGIN RETURN pgcrypto.verify_hmac( signature, payload::TEXT, secret_key::BYTEA ); END; $$ LANGUAGE plpgsql; -- 回调处理器增强 DO $$ BEGIN IF verify_signature(event_data, event_signature) THEN PERFORM process_callback(event_data); ELSE RAISE EXCEPTION 'Invalid signature'; END IF; END; $$; 权限隔离模型sql-- 最小权限回调账户 CREATE ROLE callback_executor NOLOGIN; GRANT EXECUTE ON FUNCTION handle_callback() TO callback_executor; GRANT USAGE ON SCHEMA callbacks TO callback_executor; -- 使用SECURITY DEFINER函数 CREATE OR REPLACE FUNCTION handle_callback() RETURNS VOID AS $$ $$ LANGUAGE plpgsql SECURITY DEFINER; 监控诊断方案回调追踪模板sql-- 启用详细日志记录 ALTER SYSTEM SET log_statement = 'all'; ALTER SYSTEM SET log_min_duration_statement = 100; -- 记录>100ms回调 -- 回调性能视图 CREATE VIEW callback_metrics AS SELECT event_type, count(*) AS total_calls, avg(execution_time) AS avg_time, max(execution_time) AS max_time, (SELECT COUNT(*) FROM callback_errors) AS errors FROM callback_logs GROUP BY event_type; 异常处理流程mermaidgraph TD A[回调执行] --> B{成功?} B -->|是| C[更新状态为COMPLETED] B -->|否| D[记录错误日志] D --> E{重试次数<3?} E -->|是| F[延迟重试] E -->|否| G[发送告警通知] 典型案例:电商订单系统改造​​背景​​:某电商平台需要实现订单状态变更自动通知供应链系统​​回调方案​​:sql-- 创建订单状态变更触发器 CREATE TRIGGER order_status_trigger AFTER UPDATE OF status ON orders FOR EACH ROW WHEN (NEW.status = 'SHIPPED') EXECUTE FUNCTION notify_supply_chain(); -- 回调处理器实现 CREATE OR REPLACE FUNCTION notify_supply_chain() RETURNS TRIGGER AS $$ DECLARE payload JSONB; BEGIN payload := json_build_object( 'order_id', NEW.id, 'sku_list', array_agg(DISTINCT item_sku), 'total_weight', SUM(item_weight) ); PERFORM pg_notify( 'supply_chain_channel', encode(payload::BYTEA, 'escape') ); RETURN NULL; END; $$ LANGUAGE plpgsql; ​​实施效果​​:供应链响应时间从分钟级降至秒级减少人工干预操作85%异常订单处理自动化率达到92%最佳实践指南​​设计原则​​:单回调处理时间<200ms重试次数不超过3次保持幂等性设计​​监控基线​​:text| 指标 | 正常阈值 | 告警阈值 | |---------------------|---------------|---------------| | 回调成功率 | >99.5% | <99% | | 平均响应时间 | <150ms | >500ms | | 队列积压量 | <1000 | >5000 | ​​版本兼容策略​​:使用语义化版本控制保留至少两个历史版本提供回滚机制通过合理应用GaussDB的回调机制,某金融机构实现了:实时风险监控响应速度提升6倍自动化交易对账覆盖率98%系统间集成成本降低70%建议重点关注​​异步处理​​和​​安全验证​​机制,在保证系统稳定性的前提下实现高效回调交互。作者:兮酱的探春
总条数:1666 到第
上滑加载中