-
【摘要】 在当前GaussDB(DWS)的能力中主要支持两种过程化SQL语言,即基于PostgreSQL的PL/pgSQL以及基于Oracle的PL/SQL。本篇文章我们通过匿名块,函数,存储过程向大家介绍一下GaussDB(DWS)对于过程化SQL语言的基本能力。前言 GaussDB(DWS)中的PLSQL语言,是一种可载入的过程语言,其创建的函数可以被用在任何可以使用内建函数的地方。例如,可以创建复杂条件的计算函数并且后面用它们来定义操作符或把它们用于索引表达式。 SQL被大多数数据库用作查询语言。它是可移植的并且容易学习。但是每一个SQL语句必须由数据库服务器单独执行。 这意味着客户端应用必须发送每一个查询到数据库服务器、等待它被处理、接收并处理结果、做一些计算,然后发送更多查询给服务器。如果客户端和数据库服务器不在同一台机器上,所有这些会引起进程间通信并且将带来网络负担。 通过PLSQL语言,可以将一整块计算和一系列查询分组在数据库服务器内部,这样就有了一种过程语言的能力并且使SQL更易用,同时能节省的客户端/服务器通信开销。客户端和服务器之间的额外往返通信被消除。客户端不需要的中间结果不必被整理或者在服务器和客户端之间传送。多轮的查询解析可以被避免。 在当前GaussDB(DWS)的能力中主要支持两种过程化SQL语言,即基于PostgreSQL的PL/pgSQL以及基于Oracle的PL/SQL。本篇文章我们通过匿名块,函数,存储过程向大家介绍一下GaussDB(DWS)对于过程化SQL语言的基本能力。匿名块的使用 匿名块(Anonymous Block)一般用于不频繁执行的脚本或不重复进行的活动。它们在一个会话中执行,并不被存储。在GaussDB(DWS)中通过针对PostgreSQL和Oracle风格的整合,目前支持以下两种方式调用,对于Oracle迁移到GaussDB(DWS)的存储过程有了很好的兼容性支持。√ Oracle风格-以反斜杠结尾:语法格式:[DECLARE [declare_statements]] BEGIN execution_statements END; /执行用例:postgres=# DECLARE postgres-# my_var VARCHAR2(30); postgres-# BEGIN postgres$# my_var :='world'; postgres$# dbms_output.put_line('hello '||my_var); postgres$# END; postgres$# / hello world ANONYMOUS BLOCK EXECUTE√ PostgreSQL风格-以DO开头,匿名块用$$包起来:语法格式:DO [ LANGUAGE lang_name ] code;执行用例:postgres=# DO $$DECLARE postgres$# my_var char(30); postgres$# BEGIN postgres$# my_var :='world'; postgres$# raise info 'hello %' , my_var; postgres$# END$$; INFO: hello world ANONYMOUS BLOCK EXECUTE这时细心的小伙伴们就会发现,GaussDB(DWS)不仅支持了Oracle的PL/SQL的兼容性支持,对于Oracle高级包中的dbms_output.put_line函数也做了支持。所以我们也可以将两个风格混用,发现也是支持的。(^-^)Vpostgres=# DO $$DECLARE postgres$# my_var VARCHAR2(30); postgres$# BEGIN postgres$# my_var :='world'; postgres$# dbms_output.put_line('hello '||my_var); postgres$# END$$; hello world ANONYMOUS BLOCK EXECUTE函数的创建 既然匿名块GaussDB支持了Oracle和PostgreSQL两种风格的创建,函数当然也会支持两种啦。 下面我们一起来看看具体的使用吧!(。ì _ í。)√ PostgreSQL风格:语法格式:CREATE [ OR REPLACE ] FUNCTION function_name ( [ { argname [ argmode ] argtype [ { DEFAULT | := | = } expression ]} [, ...] ] ) [ RETURNS rettype [ DETERMINISTIC ] | RETURNS TABLE ( { column_name column_type } [, ...] )] LANGUAGE lang_name [ {IMMUTABLE | STABLE | VOLATILE } | {SHIPPABLE | NOT SHIPPABLE} | WINDOW | [ NOT ] LEAKPROOF | {CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT } | {[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER | AUTHID DEFINER | AUTHID CURRENT_USER} | {fenced | not fenced} | {PACKAGE} | COST execution_cost | ROWS result_rows | SET configuration_parameter { {TO | =} value | FROM CURRENT }} ][...] { AS 'definition' | AS 'obj_file', 'link_symbol' }执行用例:定义函数为SQL查询的形式:postgres=# CREATE FUNCTION func_add_sql(integer, integer) RETURNS integer postgres-# AS 'select $1 + $2;' postgres-# LANGUAGE SQL postgres-# IMMUTABLE postgres-# RETURNS NULL ON NULL INPUT; CREATE FUNCTION postgres=# select func_add_sql(1, 2); func_add_sql -------------- 3 (1 row)定义函数为plpgsql语言的形式:postgres=# CREATE OR REPLACE FUNCTION func_add_sql2(a integer, b integer) RETURNS integer AS $$ postgres$# BEGIN postgres$# RETURN a + b; postgres$# END; postgres$# $$ LANGUAGE plpgsql; CREATE FUNCTION postgres=# select func_add_sql2(1, 2); func_add_sql2 --------------- 3 (1 row)定义返回为SETOF RECORD的函数:postgres=# CREATE OR REPLACE FUNCTION func_add_sql3(a integer, b integer, out sum bigint, out product bigint) postgres-# returns SETOF RECORD postgres-# as $$ postgres$# begin postgres$# sum = a + b; postgres$# product = a * b; postgres$# return next; postgres$# end; postgres$# $$language plpgsql; CREATE FUNCTION postgres=# select * from func_add_sql3(1, 2); sum | product -----+--------- 3 | 2 (1 row)√ Oracle风格:语法格式:CREATE [ OR REPLACE ] FUNCTION function_name ( [ { argname [ argmode ] argtype [ { DEFAULT | := | = } expression ] } [, ...] ] ) RETURN rettype [ DETERMINISTIC ] [ {IMMUTABLE | STABLE | VOLATILE } | {SHIPPABLE | NOT SHIPPABLE} | {PACKAGE} | {FENCED | NOT FENCED} | [ NOT ] LEAKPROOF | {CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT } | {[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER | AUTHID DEFINER | AUTHID CURRENT_USER } | COST execution_cost | ROWS result_rows | SET configuration_parameter { {TO | =} value | FROM CURRENT ][...] { IS | AS } plsql_body /执行用例:定义为Oracle的PL/SQL风格的函数:实例1:postgres=# CREATE FUNCTION func_add_sql2(a integer, b integer) RETURN integer postgres-# AS postgres$# BEGIN postgres$# RETURN a + b; postgres$# END; postgres$# / CREATE FUNCTION postgres=# call func_add_sql2(1, 2); func_add_sql2 --------------- 3 (1 row)实例2:postgres=# CREATE OR REPLACE FUNCTION func_add_sql3(a integer, b integer) RETURN integer postgres-# AS postgres$# sum integer; postgres$# BEGIN postgres$# sum := a + b; postgres$# return sum; postgres$# END; postgres$# / CREATE FUNCTION postgres=# call func_add_sql3(1, 2); func_add_sql3 --------------- 3 (1 row)若想使用Oracle的PL/SQL风格定义OUT参数需要使用到存储过程,请看下面章节。存储过程的创建存储过程与函数功能基本相似,都属于过程化SQL语言,不同的是存储过程没有返回值。※ 需要注意的是目前GaussDB(DWS)只支持Oracle的CREATE PROCEDURE的语法支持,暂时不支持PostgreSQL的CREATE PROCEDURE语法支持。× PostgreSQL风格: 暂不支持。√ Oracle风格:语法格式:CREATE [ OR REPLACE ] PROCEDURE procedure_name [ ( {[ argmode ] [ argname ] argtype [ { DEFAULT | := | = } expression ]}[,...]) ] [ { IMMUTABLE | STABLE | VOLATILE } | { SHIPPABLE | NOT SHIPPABLE } | {PACKAGE} | [ NOT ] LEAKPROOF | { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT } | {[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER | AUTHID DEFINER | AUTHID CURRENT_USER} | COST execution_cost | ROWS result_rows | SET configuration_parameter { [ TO | = ] value | FROM CURRENT } ][ ... ] { IS | AS } plsql_body /执行用例:postgres=# CREATE OR REPLACE PROCEDURE prc_add postgres-# ( postgres(# param1 IN INTEGER, postgres(# param2 IN OUT INTEGER postgres(# ) postgres-# AS postgres$# BEGIN postgres$# param2:= param1 + param2; postgres$# dbms_output.put_line('result is: '||to_char(param2)); postgres$# END; postgres$# / CREATE PROCEDURE postgres=# call prc_add(1, 2); result is: 3 param2 -------- 3 (1 row)经过以上对GaussDB(DWS)过程化SQL语言的简单介绍,我们大致了解了在GaussDB(DWS)中匿名块,函数,存储过程的创建,下面将简单介绍一下在过程化SQL语言中的一些简单的语法介绍。基本语法介绍赋值:支持 = 与 := 两种赋值符合的使用。下面两种赋值方式都是支持的。a = b; a := b + 1;条件语句:支持IF ... THEN ... END IF; IF ... THEN ... ELSE ... END IF; IF ... THEN ... ELSEIF ... THEN ... ELSE ... END IF;其中ELSEIF也可以写成ELSIF。语法介绍:-- Case 1: IF 条件表达式 THEN --表达式为TRUE后将执行的语句 END IF; -- Case 2: IF 条件表达式 THEN --表达式为TRUE后将执行的语句 ELSE --表达式为FALSE后将执行的语句 END IF; -- Case 3: IF 条件表达式1 THEN --表达式1为TRUE后将执行的语句 ELSEIF 条件表达式2 THEN --表达式2为TRUE 后将执行的语句 ELSE --以上表达式都不为TRUE 后将执行的语句 END IF;示例:postgres=# CREATE OR REPLACE PROCEDURE pro_if_then(IN i INT) postgres-# AS postgres$# BEGIN postgres$# IF i>5 AND i<10 THEN postgres$# dbms_output.put_line('This is if test.'); postgres$# ELSEIF i>10 AND i<15 THEN postgres$# dbms_output.put_line('This is elseif test.'); postgres$# ELSE postgres$# dbms_output.put_line('This is else test.'); postgres$# END IF; postgres$# END; postgres$# / CREATE PROCEDURE postgres=# call pro_if_then(1); This is else test. pro_if_then ------------- (1 row) postgres=# call pro_if_then(6); This is if test. pro_if_then ------------- (1 row) postgres=# call pro_if_then(11); This is elseif test. pro_if_then ------------- (1 row)循环语句:支持while,for, foreach的使用。循环期间也可以适当添加循环控制语句continue, break。语法介绍:WHILE 条件表达式1 THEN --循环内需要执行的语句 END LOOP; FOR i IN result LOOP --循环内需要执行的语句 END LOOP; FOREACH var IN result LOOP --循环内需要执行的语句 END LOOP;示例:postgres=# CREATE OR REPLACE FUNCTION func_loop(a integer) RETURN integer postgres-# AS postgres$# sum integer; postgres$# var integer; postgres$# BEGIN postgres$# sum := a; postgres$# WHILE sum < 10 LOOP postgres$# sum := sum + 1; postgres$# END LOOP; postgres$# postgres$# RAISE INFO 'current sum: %', sum; postgres$# FOR i IN 1..10 LOOP postgres$# sum := sum + i; postgres$# END LOOP; postgres$# postgres$# RAISE INFO 'current sum: %', sum; postgres$# FOREACH var IN ARRAY ARRAY[1, 2, 3, 4] LOOP postgres$# sum := sum + var; postgres$# END LOOP; postgres$# postgres$# RETURN sum; postgres$# END; postgres$# / CREATE FUNCTION postgres=# call func_loop(1); INFO: current sum: 10 INFO: current sum: 65 func_loop ----------- 75 (1 row)GOTO语句:支持goto语法的使用。语法介绍:GOTO LABEL; --若干语句 <<label>>示例:postgres=# CREATE OR REPLACE FUNCTION goto_while_goto() postgres-# RETURNS TEXT postgres-# AS $$ postgres$# DECLARE postgres$# v0 INT; postgres$# v1 INT; postgres$# v2 INT; postgres$# test_result TEXT; postgres$# BEGIN postgres$# v0 := 1; postgres$# v1 := 10; postgres$# v2 := 100; postgres$# test_result = ''; postgres$# WHILE v1 < 100 LOOP postgres$# v1 := v1+1; postgres$# v2 := v2+1; postgres$# IF v1 > 25 THEN postgres$# GOTO pos1; postgres$# END IF; postgres$# END LOOP; postgres$# postgres$# <<pos1>> postgres$# /* OUTPUT RESULT */ postgres$# test_result := 'GOTO_base=>' || postgres$# ' v0: (' || v0 || ') ' || postgres$# ' v1: (' || v1 || ') ' || postgres$# ' v2: (' || v2 || ') '; postgres$# RETURN test_result; postgres$# END; postgres$# $$ postgres-# LANGUAGE 'plpgsql'; CREATE FUNCTION postgres=# postgres=# SELECT goto_while_goto(); goto_while_goto ------------------------------------------- GOTO_base=> v0: (1) v1: (26) v2: (116) (1 row)异常处理:语法介绍:[<<label>>] [DECLARE declarations] BEGIN statements EXCEPTION WHEN condition [OR condition ...] THEN handler_statements [WHEN condition [OR condition ...] THEN handler_statements ...] END; 示例:postgres=# CREATE TABLE mytab(id INT,firstname VARCHAR(20),lastname VARCHAR(20)) DISTRIBUTE BY hash(id); CREATE TABLE postgres=# INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones'); INSERT 0 1 postgres=# CREATE FUNCTION fun_exp() RETURNS INT postgres-# AS $$ postgres$# DECLARE postgres$# x INT :=0; postgres$# y INT; postgres$# BEGIN postgres$# UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones'; postgres$# x := x + 1; postgres$# y := x / 0; postgres$# EXCEPTION postgres$# WHEN division_by_zero THEN postgres$# RAISE NOTICE 'caught division_by_zero'; postgres$# RETURN x; postgres$# END;$$ postgres-# LANGUAGE plpgsql; CREATE FUNCTION postgres=# call fun_exp(); NOTICE: caught division_by_zero fun_exp --------- 1 (1 row) postgres=# select * from mytab; id | firstname | lastname ----+-----------+---------- | Tom | Jones (1 row) postgres=# DROP FUNCTION fun_exp(); DROP FUNCTION postgres=# DROP TABLE mytab; DROP TABLE总结: GaussDB(DWS)对于过程化SQL语言的支持主要在PostgreSQL与Oracle上做了兼容,同时针对Oracle的一些高级包以及一些Oracle独有的语法也做了一定支持。在迁移Oracle或者PostgreSQL时,对于函数或存储过程的迁移可以减少为了兼容导致的额外工作量。 至此已经将GaussDB(DWS)中的匿名块,函数,存储过程的创建以及基本使用介绍的差不多了。当然GaussDB(DWS)对于过程化SQL语言的支持不止如此,在接下来的时间里,还将逐步向大家介绍游标,用户自定义类型等章节哟~ヾ(◍°∇°◍)ノ゙ 想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~原文链接:https://bbs.huaweicloud.com/blogs/265742
-
【摘要】 GaussDB在大规模集群上运行的过程中,随着时间推移,部分节点可能会出现性能严重下降的情况。此时这些节点仍然能对外提供服务,但响应明显变慢,处理同样的请求所需时间较其他正常节点大很多,从而影响了整个集群的性能。这样的节点称为“亚健康节点”,或“慢节点”。背景慢实例简介 GaussDB在大规模集群上运行的过程中,随着时间推移,部分节点可能会出现性能严重下降的情况。此时这些节点仍然能对外提供服务,但响应明显变慢,处理同样的请求所需时间较其他正常节点大很多,从而影响了整个集群的性能。这样的节点称为“亚健康节点”,或“慢节点”。 大规模集群中亚健康节点的准确识别本身是一个难题。经过深入观察和分析,我们发现节点响应慢还可能是业务倾斜导致的,例如对某些数据的集中访问,或是某些业务需要较多的运算,导致某些节点的工作量剧增。此外,这样的节点不属于性能低下的慢节点。如果单纯通过响应时间判断慢节点容易导致误判,而频繁的误报警则会降低用户的体验,增加工程人员的排查负担。本特性实现需要在充分考虑业务倾斜等因素的前提下,迅速、准确地识别出慢节点。慢实例的获取机制 Gauss DB中的等待状态视图pg_thread_wait_status可以显示每个实例上各线程的瞬时状态。通过深入分析其监控原理,结合现网的时间,我们发现通过该视图有效地发现响应慢的节点。平均低来讲,节点响应越慢,上面的等待事件就越多,二者存在正相关,因此可以通过查询该视图中各节点的等待事件数量来衡量节点响应的快慢。根据现网的经验,如果等待状态视图中等待的DN大多数是同一个节点上的DN,则这个节点大概率是慢节点。 以图1所示的工行集群上的等待状态视图查询结果为例,890这个节点上的事件等待数量在连续多次查询中均为全网之冠,且占全网等待数量的比例在50%以上。最终硬件检测表明890节点确实存在硬件故障,导致运行缓慢。 GaussDB的数据节点(Datanode)采用主备模式。如果一个物理机器上的Datanode全是备机,该物理机器并不会直接对外提供服务,也无法通过pg_thread_wait_status视图查询等待事件的数量。但是主DN需要向备DN同步日志。如果这样的“全备机”物理机器恰好是一个慢节点,由于同步日志缓慢,会影响主DN的性能。虽然主机上的pg_thread_wait_status视图等待数量会有所增加,但因为该物理机器对应的主DN被分散到多个物理节点上,单个物理节点上等待数量可能并不能达到阈值。针对这种情况,还需要对等待视图中主DN对应的备DN做进一步计算,间接得到“全备机”物理机器的等待数量。图1.1 通过等待视图识别慢节点 由于主备切换,数据倾斜等原因,集群中各节点的负载并不均衡,负载高可能也会造成节点响应缓慢。为了避免误报,需要消除负载倾斜对于亚健康节点识别的干扰。为了简化判断,我们进一步假定负载倾斜和故障不会同时出现在同一节点上。也就是说,如果一个节点的负载水平较高,响应慢被认为是正常的,不会被当成慢节点而告警。 节点的负载水平可以通过数据的访问量和运算量来衡量,而后者可以通过IO的数量、消耗CPU时间以及网络数据流量来进行量化计算。GaussDB内部对IO的数量(block数)、CPU时间和网络数据流量进行了打点统计,并通过多种视图呈现出来。通过定期访问这些视图,可以获得节点在单位时间内的工作量,进而获得每个节点在各个时段的负载水平。 与等待事件的数量一样,“全备机”的物理机器的负载水平,也需要通过其对应的主DN的负载水平间接推算。由于备机很少参与运算,主要工作是接收日志,因此主要通过主DN的IO数量和网络流量来衡量备机的繁忙程度。并且由于间接推算的准确性相对较差,只有“全备机”节点才会采用这种方式。只要某台物理机器上有主DN的存在,就直接采用主DN的数据。对慢实例进行监控场景分析 根据客户需求分析,可以将客户需求定义为集群中慢节点发生次数与频率的时间序列及详情展示。包含以下主要功能:开启关闭慢实例检测功能配置慢实例检测参数采集慢实例信息上报DMS数据库将慢实例触发次数及慢实例名称等信息以时间序列展示在页面上方案整体架构设计图 2.1 慢实例监控整体架构图 整体方案属于慢实例检测端到端的新增项,并调用健康检查相关模块进行慢实例数据生产。 ① dms-agent调用健康检查脚本并传入collection下发的配置参数,主要操作包括初始启动调用,以及进程中断重启调用; ② 健康检查脚本采集慢节点数据到用户数据库中 ③ dms-agent通过查库获取相关数据 ④ dms-agent根据dms-collection下发的上报频率进行数据上报 ⑤ dms-collection接收dms-agent上报的数据进行dms数据库入库等。 ⑥ dms-monitoring查询dms数据库获取慢节点数据展示到dws-console前端页面 ⑦ dws-console通过dms-monitoring下发启停配置到dms-agent,并通过dms-monitoring将启停信息持久化到dms数据库中; dws-console通过dms-monitor持久化监控参数配置到dms数据库中。 ⑧ dms-monitoring将修改后的配置通过grpc下发到dms-agent,dms-agent按照新的上报频率以及配置进行数据采集上报 ⑨ dms-agent在配置更新后,按照新配置进行健康检查脚本的重启操作。慢实例数据详解及数据处理慢实例表结构设计drop table if exists dms_mtc_cluster_slow_inst cascade; create table dms_mtc_cluster_slow_inst( ctime bigint not null, -- 采集时间 virtual_cluster_id int not null, -- 虚拟集群id check_time bigint, -- 检测时间 host_id int, -- 主机id host_name varchar(128), -- 主机名 inst_id varchar(64), -- 集群分配的实例id inst_name varchar(128), -- 实例名称 primary key(virtual_cluster_id, inst_id, check_time) );慢实例数据处理 数据入库Dms-collection接收agent上报的数据进行入库insert into dms_mtc_cluster_slow_inst (ctime, virtual_cluster_id, check_time, host_id,host_name, host_id, inst_name) values (#{sinst.ctime}, #{ sinst.virtual_cluster_id}, #{sinst.check_time},#{sinst.host_id}, #{sinst.host_name}, #{sinst.inst_id}, #{sinst.inst_name});数据查询 Dms-monitoring查询慢实例时间序列以及数量select check_time, count(*) as slow_inst_num from dms_mtc_cluster_slow_inst where virtual_cluster_id = (select virtual_cluster_id from dms_meta_cluster where cluster_id = #{clusterId}) and check_time >= #{from} and check_time <= #{to} group by check_time; Dms-monitoring查询慢实例数据详情select t1.check_time, t1.host_name, t1.inst_name, t2.trigger_times from (select check_time, host_name, inst_name from dms_mtc_cluster_slow_inst where virtual_cluster_id = ( select virtual_cluster_id from dms_meta_cluster where cluster_id = #{clusterId}) and check_time = #{checkTime}) as t1 inner join (select inst_name, count(*) as trigger_times from dms_mtc_cluster_slow_inst where virtual_cluster_id = (select virtual_cluster_id from dms_meta_cluster where cluster_id = #{clusterId}) and check_time >= (#{checkTime} - 86400000) and check_time <= #{checkTime} group by inst_name) as t2 on t1.inst_name = t2.inst_name; 慢实例数据展示以及页面配置慢实例页面配置 可通过DMS监控配置页面对慢实例采集频率以及慢实例脚本各项参数进行设置,如下图所示图4.1 慢实例页面配置慢实例时间线以及数量展示 可通过DMS监控页面查看24小时内各个时间点的慢实例个数,如下图所示图4.2 慢实例数据展示慢实例数据详情 可点击任一条形图,展示当前时间点慢实例数据详情,包括检测时间、节点名称、实例名称、检测次数等,如下图所示图4.3 慢实例数据详情展示 想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料~原文链接:https://bbs.huaweicloud.com/blogs/255906
-
与互联网产品的立项模式类似,当我们定义设计一款新产品时,首先需要对用户做需求分析,归纳整理综合分析用户的需求,定义我们的产品定位,功能,业务逻辑,使用界面等等。因此,为了设计好数据库智能监控系统,我们需要对数据库监控系统的目标用户做需求分析,收集用户诉求,挖掘用户潜在需求,绘制典型用户画像。最后,设计数据库监控系统的实现架构,将典型用户的各种需求纳入到产品的设计架构管道中。数据库智能监控系统的用户 实际应用场景中,数据库监控系统的用户可能会有很多种不同的角色。不同公司因为组织架构不同,可能存在更细分或者更聚合的用户角色。但是总的来说可以归纳整理为如下三种用户类型:应用开发(APP DEV)运维工程师(SRE)数据库管理员(DBA) 应用开发工程师角色:主要负责开发云应用中的业务SQL,对云服务的功能和性能负责。同时需要保证写出的SQL高效优质,不会对集群造成额外的资源消耗和时间消耗。因此,应用开发工程师,需要能够对新开发的SQL进行监控,了解该新增查询语句的执行效率以及资源消耗情况。运维工程师角色:主要负责保证数据库集群的长期稳定运行。需要分别从资源消耗和系统负载两个角度对数据库系统进行评估。需要能够配置数据库的告警场景,并且可以看到实时或预测的数据库告警信息,将发现的问题报告给数据库管理员角色做进一步处理。总的来说,运维工程师角色会监控大量的数据库集群,他不会对每一个集群做非常深入的分析,而是更多的会以问题发现者的角色出现。数据库管理员角色:主要负责定位数据库问题的根因并且提供相应的解决方案。数据库管理员需要是数据库领域的专家,熟悉数据库的方方面面,他可以从多个维度分析数据库监控数据,定位数据库故障,并提供解决方案。 需要说明的是,以上三种角色并不是指实际生产环境中的岗位,而是为了方便分析用户需求而归纳总结出来的典型角色符号。实际生产环境中,可能出现三种角色为同一个人的场景,或者SRE岗位会身兼SRE与DBA角色的场景。我们这里将用户区分为三种角色,主要是为了方便我们做需求分析并且构建对应的人物画像,从而进一步锁定对应角色人物所需要的工具。最终,呈现给大家一个思路清晰的数据库监控系统开发概念脉络。数据库智能监控系统工具及应用场景 通过上面的抽象和梳理,我们发现在数据库监控运维过程中三种角色分别对应着不同的需求,而不同的需求必将导致不同工具或者同一个工具的不同侧重点。下面我们围绕三种角色,分别展开详细介绍其将要用到的工具:应用开发角色,他们只关心自己写的SQL是否高效,是否有利用到集群的各种优化特性,是否占用了集群的过多资源?因此,他需要一个能够让他评估其所写SQL执行效率的工具,也就是WebSQL工具,允许用户简单的连接到数据库,并且执行SQL语句。WebSQL可以返回SQL语句的执行结果,也可以返回其执行计划,帮助应用开发角色,了解其SQL语句的执行效率。同时,用户的SQL语句并不是简单的单条语句执行的,而是需要将其放在整个作业流中去执行的。那么衡量其在作业流中的执行时间和资源消耗的基线就变得非常重要。因此,我们就需要查询监控可以针对特性的SQL记录其执行时间和资源的消耗,并且计算最大值,最小值和平均值,作为比对基线,来进一步帮助用户评估其SQL的执行效率。在用户现场,因为资源隔离需求,用户的作业是需要绑定到某个工作负载队列执行的,那么工作敷在队里的资源配置,以及工作负载队列的负载水平等数据又变得非常重要,新加的SQL语句是否会造成工作负载队列的超载?当前工作敷在队列的资源是否合理,这个都需要应用开发角色在新开发的应用上线前有个直观的了解。系统运维角色角色(SRE),他们关心云上数量庞大的数据库系统的长稳运行,基于这个需求,我们打算提供三个方面的工具来解决问题。 健康指数指标,该指标是一个复合指标,该指标主要由两方面的指标支撑,资源消耗指数和数据库系统负载指数。而这两种指标又有其更下一层的原子指标和延伸指标支撑。集群健康指数的计算需要设计一套相应的数学模型,以该模型为基础我们就可量化系统的健康指数,从而可以使系统管理员非常简单的从云上数以百计的数据库中快速发现有问题的数据库。 除了健康指数这样需要系统管理员亲自去查看的被动指标以外,DMS还会进一步提供覆盖全面的告警能力。DMS将从三个层次上提供数据库的告警能力,(1)在dms-agent端,通过日志分析的手段,实时分析dms-agent所处节点上,操作系统以及数据库的日志,当发现威胁关键词后,立刻触发告警,通过相应渠道上报到告警平台;(2)在DMS服务端,因为DMS拥有数据库集群的全部监控数据,通过数据分析手段和数据库专业知识,我们将能设计相应的告警规则,周期性的对数据库集群做检查,发现问题后直接触发告警;(3)对于DMS采集的数据库集群指标数据,能够作为阈值告警的指标,全部对接CES,通过CES服务做阈值告警。以上三种告警的配置和展示都需要在DMS的前端页面上呈现。 人工智能与云计算有的天然的联系,当数据库上云后,人工智能与数据库运维的交叉节点AIOps就顺理成章的出现了。因为DMS拥有数据集群的全部监控数据,因此使用历史监控数据对集群的工作模式做判别,推荐最优化的配置参数;对数据库磁盘的空间增长趋势做预测,提前通知用户扩容或运维需求等等。在人工智能的加持下这一切都变成为可能。 数据库管理员角色(DBA),数据库管理员一直都是数据库的大管家,在传统的数据中心里,他们负责数据库的性能优化,也负责数据库的长稳运行,有时候甚至也要帮助应用开发工程师优化SQL。但是在云时代,数据库管理员的工作分工会变得更精细,应用开发和系统管理员分担了数据库管理的一部分工作,从而使得数据库管理员角色职责变的更纯粹。数据库管理员作为一个数据库领域的专家,他将负责定位数据库问题的根因,以及提供解决问题的方法。系统管理员+数据库管理员两个角色最终就形成了发现问题,分析问题,解决问题的任务闭环。因此,在云上,SRE岗位往往会包含SRE+DBA两个角色的职责。 DBA是一个数据库专家,也是一个使用数据库工具定位各种数据库问题的大师。针对问题根因定位,他将需要故障分析工具和故障自愈工具两类工具。其中,故障分析工具,将会提供各种监控数据和数据的不同可视化形式,为数据库管理员快速定位问题根因提供帮助。故障自愈类工具,则是将数据库管理员过去定位问题,解决问题经验的固化。未来随着我们对DBA工作方法的进一步了解,将会有越来越多的自愈类工具。 数据库管理员另一类重要的职责就是提供故障的解决方案,这一块是运维系统非常重要的一环。再好的故障定位工具,定位到的问题,如果最后没有解决方案,那么最终还是不能真正帮助到用户。因此,我们需要建立一套问题根因-解决方案的专业搜索引擎,帮助用户也是帮助我们加速解决问题的流程,缓解一线客户支持工作人员的工作强度。 本文是介绍云上的数据库监控运维体系设计的核心概念的三篇文章之二,尝试从概念和逻辑上推导了基于用户角色的数据库智能监控系统的可能应用场景。有了这个基本框架,则我们后续所需要做的工作和工具都变得清晰可见。愿我们的期待早日成为显示,让云端的数据库运维工作变得更轻松与智能。想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~原文链接:https://bbs.huaweicloud.com/blogs/269120
-
【摘要】 NetBackup是Veritas公司软件产品,为各种平台提供完整而灵活的数据保护解决方案。这些平台包括Microsoft Windows、UNIX、Linux 等系统。利用NetBackup可以备份、归档和还原计算机上的文件、文件夹或目录以及卷或分区。当前DWS支持NBU介质备份恢复,本文介绍DWS对接NBU备份故障排除方法。部署方式假如已有3节点DWS集群,Roach(DWS备份工具)将本节点的集群数据通过TCP发送到远端NBU Media Server机器。每台NBU Media Server上面同时安装NBU Client,并部署Roach client组件,后者接收集群内Roach进程发来的备份数据,不落盘方式通过XBSA接口转发给本机的NBU Client,完成NBU备份。恢复流程也类似,只是数据流相反。在DWS备份过程中,一般故障主要出自以下三处:Roach agent: 即集群节点内,直接查看集群备份日志($GAUSSLOG/roach/)即可Roach client: 此插件主要负责数据收发,日志路径启动时通过-l参数指定,进入该路径查询即可NBU软件端: 可通过下文定位方式排查故障环境校验当进行NBU非侵入式备份时,考虑到集群备份过于重量,可以先通过指定小文件测试环境连通性,保证NBU配置gs_roach uploadmeta --media-destination 'nbu_policy' --metadata-destination '/home/Ruby/meta' --media-type NBU --backup-key '20200903_164332' --nbu-on-remote --media-server 192.168.243.65 --client-port 9000 注:--media-destination为NBU策略名称--backup-key为任一指定时间戳即可--media-server为任意一台部署了roach client插件的ip地址--client-port为roach client开放的端口--metadata-destination为上传指定文件路径,其中将测试上传文件重名名为metadata.tar.gz,并放置在/home/Ruby目录下,并非/home/Ruby/meta目录下如果能备份成功,则说明所连接的media server配置无问题,如果存在失败,则NBU端配置有问题,需要按照后续说明寻求原因。故障定义故障排除的第一步是定义问题。在NBU系统的安装、配置、运行过程中,出现了与正确预期不同的结果,即可认为是出现了故障;有时候,这要求我们知道正确的情况应该是什么样的。在NBU的交付和使用中常见的故障主要分为种:一是软件安装和配置阶段,比如软件安装不成功、对接不成功、某模块功能不可用等等,这一阶段的错误一般没有具体的错误码,需要结合交付人员的经验和系统日志进行排错,这种故障属于一次性的故障,在排除之后再次出现的可能性很小;二是在系统部署完成后,数据备份业务上线、备份和恢复任务执行时报错,比如接入client失败、存储单元写入数据失败、找不到client服务器等等;这种故障console会提供错误码(error code),维护人员可以根据错误进行初步的定位,这种故障属于日常性的故障,和环境中多种因素有关,备份系统自身之外的业务环境发生细微的变化都有可能导致故障的出现。故障排除过程要排除问题,必须知道发生了什么错误。错误消息通常是指出哪里出现故障的手段。所以,我们要做的第一件事就是查找错误消息。如果在界面上没有看到错误消息,但仍怀疑有问题,请检查报告和日志。NetBackup 提供了广泛的报告和日志记录工具,这些工具可提供错误消息,直接指出解决方案。日志还可显示什么运行良好以及当发生问题时 NetBackup 正在执行什么操作。综上,NBU备份与恢复故障排除过程如下:1、确认服务器和client运行的是受支持的操作系统或应用版本;具体信息参看NBU兼容性列表;2、复现故障,获取故障信息;获取信息的渠道有错误码、Job Details、日志等;3、根据获取的信息进行故障定位和排除;故障排除方法使用状态码每一个备份和恢复任务都是一个activity,在activity monitor一栏中可以监控到它们。由任务监视看出该任务的ID、执行何种操作、状态、返回值、Server和Client是谁、通过哪一个Policy和Schedule去执行的。具体可显示多长时间的任务,要看NetBackup全局属性中的设置。每个任务有以下几个状态:Queued 任务正在排队Active 任务正在执行Done 任务执行完毕在activity的执行过程中,每一个任务结果都对应着一个状态代码,0代表成功,非0代表故障。返回值是一个非常有用的参数,通过返回值,可以通过错误代码查找手册中建议的相关调整建议,这对于问题检查和性能调整是非常有用的。页面中获取位置如下:以下链接提供了NBU备份任务status code list:https://www.veritas.com/content/support/en_US/doc/44037985-127664609-0/v15096675-127664609根据获取到的status code可以初步定位错误原因使用Job details与状态码类似,Job details与activity也是一对一;不同的是,Job details比状态码提供的信息更多,对于常见的故障,使用Job details可以完成故障的原因定位和排除。双击一个activity,选择detailed status,在status一栏即可获取更多的细节信息。找到关键错误信息(通常是红色字体或红色字体的上下文),提炼出关键字,在google上搜索,互联网上有大量的相同错误场景和解决办法。使用日志以上使用状态码和Job details进行故障排除的办法停留在初级阶段,通常只对简单故障有效;对于复杂问题,如果解决不了则需要搜集日志进行分析。在NBU系统中,日志级别共分为6级,分别为0-5,以下为日志级别对应的要记录的信息:0:非常重要的少量诊断消息和调试消息1:该级别增加详细的诊断消息和调试消息2:增加进度消息3:增加提示性转储消息4:增加功能进入和退出消息5:最详细的信息:记录所有信息日志等级调整方式如下:1、console界面调整2、vi /usr/openv/netbackup/bp.conf, 在末尾调加如下配置VERBOSE = 5NBU系统针对每一个进程都有一个独立的目录来存放,但是在默认情况下不创建,所有如果想要搜集这些日志,工程师需要手动创建这些目录。目录格式为/usr/openv/netbackup/logs/进程名;以bpcd程序为例,执行以下命令创建子目录:mkdir /usr/openv/netbackup/logs/bpcd或者使用NBU提供的批量创建脚本,一键创建所有日志目录,执行以下命令:sh /usr/openv/netbackup/logs/mklogdir在搜集日志时,NBU针对性地为每个进程创建一个日志子目录,来实现进程级别的日志分析,那么我们需要先知道NBU常用的进程有哪些:admin:管理命令。bpbrm:NetBackup 备份和还原管理器。bpcd:NetBackup client后台驻留程序或管理器。bpdm:NetBackup 磁盘管理器。bpdbm:NetBackup 数据库管理器。此进程仅在主服务器上运行。bprd:NetBackup 请求管理器,对客户机和备份、恢复、归档等管理请求作出响应。vnetd:Veritas 网络后台驻留程序。bpbackup:在UNIX client上,当用户启动备份时,此程序与主服务器上的bprd通信。在获取了日志之后,在各个文件中搜索fail、error、can not、freeze等关键字,进行故障原因定位NBU常用维护命令用命令行启动netbackup服务进程/usr/openv/netbackup/bin/bp.start_all用命令行停止netbackup服务进程/usr/openv/netbackup/bin/bp.kill_all用命令行清除host缓存/usr/openv/netbackup/bin/bpclntcmd -clear_host_cache # 清除缓存 cd /usr/openv/var/host_cache/ # 清除临时文件 rm –rf tmp mkdir tmp mv * tmp用命令行检测master和client连通性/usr/openv/netbackup/bin/admincmd/bptestbpcd -client client_hostname若可以连通,返回结果类似如下:NBU master server与NBU client 通信问题在client和master server上互相telnet对方的备份管理平面IP的1556、1372、13782三个端口,确认client服务器与master server通信正常netstat –an | grep 1556 netstat –an | grep 1372 netstat –an | grep 13782检查NBU服务及进程/usr/openv/netbackup/bin/./bpps -xMedia server不是认证的主机此为client上对media server的信任配置问题。在console上点击host properties>client,找到故障客户端,双击client,在弹出界面点击servers一栏,在additional server配置中添加media server的主机名存储单元不可用出现“存储单元不可用”故障信息可能有以下几种情况:1、存储单元已满2、此存储单元上处于排队状态的备份任务过多3、client与存储单元归属的media server无法通信 想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~原文链接:https://bbs.huaweicloud.com/blogs/265919
-
一、前言 在数据仓库平台建设过程中,数据的加载、卸载,各层数据模型之间的数据流转,业务规则的实现等等数据加工过程都会以ETL任务的方式实现。 构建ETL子系统是数据仓库系统实施的一个非常重要的环节,在仓库平台建设过程中搭建一个完整、标准的ETL子系统是数据仓库平台建设的基础性目标之一。 ETL是Extraction(数据抽取),Transform(数据转换)和Loading(数据加载)这三个数据处理动作的缩写,也是早期数据仓库建设的数据流转处理顺序,因此形成的专用术语沿用至今。但是随着作为数据仓库核心的数据库引擎技术的不断发展,ETL模式也在不断发展和改变,逐渐形成了E-L-T,E-T-L-T等不同形式。对于GaussDB DWS为代表的MPPDB数据仓库平台,则多以ELT或是ETLT模式为主来构建ETL子系统。二、ETL子系统逻辑参考架构 ETL子系统的建设目的是将企业中的分散、零乱、标准不统一的异构数据源的业务数据整合到一起,进行必要的清洗和转换,形成高质量的统一的数据模型,或者是便于用户查询,分析和探索的维度模型。图1 数据仓库子系统参考架构2.1 数据抽取(Extraction) 数据抽取是从数据仓库的上游系统(通常是核心系统,业务系统或外部系统)进行全量或增量数据抓取的过程。而随着企业内部信息底层架构的完善和数据平台功能的划分,不同平台通常采用松耦合的方式进行关联。传统中下游系统直接到上游系统进行数据抽取的这种方式并不符合当前技术的发展趋势。一方面,下游系统直接到上游系统进行数据抽取操作牵涉到权限的开放管理,增加了上游系统的数据安全风险。另一方面,本身数据抽取操作也应当在业务系统自身正常业务完成后的时间窗口进行,以避免数据抽取时对正常作业流程的资源竞争。因此数据抽取这个环节的操作,通常是上下游系统进行接口协商,由上游系统按照接口规范进行数据卸载操作。或者对于更成熟的企业,会构建统一的数据交换平台来完成企业内部统一的数据抽取/卸载工作。 对于数据仓库平台来说,数据抽取的工作更多的是形成统一的接口规范。2.2 数据转换(Transform) 广义上的数据转换包括数据清洗,数据关联加工,数据标准化处理,数据汇总聚合等操作。大部分基于业务规则和数据模型的数据转换操作在MPPDB数据库内实现比在数据库外的ETL服务器上进行实现效率更高。而这种转换操作在数据库内通过SQL实现T过程,也比通过ETL工具实现T过程更具有标准化和开放性,适合业务人员参与T过程的开发,校验。2.3 数据加载(Loading) 对于数据仓库而言,不仅仅是数据加载,还包括数据卸载,也就是Loading和Unloading过程。典型的场景就是在数据到达的高峰期进行大量的文件加载入库的操作。而库内数据加工完成后,及时地进行数据卸载操作,形成接口文件推送给下游系统。所以高效地批量数据加载和卸载操作是数据仓库ETL系统要面对的主要挑战之一。而随着客户对实时数据仓库的需求越来越普遍,数据库和消息队列,数据流组件之间的实时数据加载和卸载的技术则是当前ETL系统构建时面临的又一个技术挑战。三、ETL子系统的两种实现架构 依托GaussDB(DWS)数据库构建ETL系统一般有两种实现方式:重ETL Server方案和MPPDB方案。如下图图2 两种架构示意图3.1 重ETL Server方案 这种方案借助专业化的ETL软件:Informatica, DataStage, Kettle等软件,采用分布式的/基于共享存储的ETL服务器集群方式部署ETL软件。在执行ETL任务的时候,数据从MPPDB读取出来,数据处理过程在ETL服务器完成,处理完结果再推送到数据库服务器,其中有些操作可以通过SQL Push down在数据库内完成。 这种方案的特点在于整个ETL开发和部署过程图形化操作和脚本化操作方式结合,基于工具过程的开发也可以对ETL过程进行基于元数据的血缘分析,影响性分析;作业自动化编排,调度方面ETL工具的功能弱于专业的调度软件。 基于ETL工具方案对于ETL开发过程来说需要专业的开发人员,要对ETL工具本身有很深入的了解,从这方面来说,过于专业化的工具门槛不利于企业内部的业务专家和分析人员介入ETL开发过程。而ETL方面对于软硬件的投入成本也是需要纳入考量的一个问题。3.2 MPPDB方案本方案中ETL服务器轻量化,生产环境一般提供主备服务器避免单点故障即可。主要特点如下:利用MPPDB并行处理引擎,海量数据ETL处理效率更高。ETL过程SQL模板化,快速开发和迭代的过程代价低;汇总层和集市层的ETL处理逻辑一般和业务规则强相关,SQL标准对于业务人员开发门槛低与第三方ETL服务器解耦,通过工具封装,可以避免过度依赖某一个ETL工具;需要对ETL脚本模板进行定制化封装式开发,为运维,优化,数据治理等过程提供底层数据。3.3简单对比 重ETL Server方案适合基于文件的数据清洗类ETL工作:对于字符集的转换处理;按照接口规范对接口数据的预处理(判断文件大小,记录行数等文件信息和属性方面的数据质量检查);文件的分组,拆分,压缩,解压缩等;以及延伸出去的文件监控和传输功能。 MPPDB方案实际上就是基于SQL的实现方案,适合数据规范化处理:如业务编码转换,业务逻辑主键生成,符合业务规范的数据转换处理;数据转换处理:汇总,聚合,过滤,关联,拆分,转换等。四、GaussDB(DWS)ETL系统实现要点 对于GaussDB(DWS)而言,大多数场合下推荐采用MPPDB方案。实现这种方案实际上要实现ETL SQL模板的封装,把ETL开发过程与外部ETL调度系统的结合进行分层处理。通过模板方式实现与调度软件,和操作系统的接口封装,把SQL实现业务的模块封装在GSQL工具中,开放给业务人员和开发人员,令其聚焦在业务实现本身,而不用在意外部环境对于ETL过程操作的影响。4.1 基于MPPDB的ETL环境逻辑视图图3 逻辑视图ETL调度 数据仓库平台的ETL作业系统是一种后台非交互方式运行的批量数据处理系统。ETL作业调度是将数据仓库系统中运行的各种后台作业自动化,并监视和控制作业的运行。使用调度软件实现作业调度。作业可以分布在多个服务器平台上,能够设定作业定义、依赖关系、顺序关系、工作组关系等,方便地对作业进行自动调度、运行和管理。 调度监管平台可以图形方式动态监视和控制作业的运行,对作业执行中出现的错误/警告提供详细的信息。ETL脚本封装GSQL是执行SQL的工具,但是与调度软件之间的结合还有一定的功能缺失,如参数解析,日志解析,异常处理等,所以需要对GSQL进行必要的封装,提高和调度软件之间的契合度。gsql模板对加工处理的etl过程进行抽象,总结,形成算法模板。指定必要的输入参数,设定会话启动的公共参数,为后续的优化和跟踪埋点打桩。调用形式调度工具->Python或其他脚本工具模板->GSQL->{.gsql}4.2 GSQL封装图4 GSQL封装示意图增加封装的必要性: GSQL和调度软件解耦:调度软件都具备调用Python/Perl/Shell脚本的能力,通过脚本封装,把GSQL和调度软件解耦,降低GSQL和调度软件的适配兼容性风险;封装模板需要考量的功能点:调度命令到GSQL运行命令的转换:调度命令相对简单,和业务逻辑相关:如业务子系统代码,算法模板代码,数据日期等;GSQL的运行参数不需要或不应当暴露在调度系统接口下:如登录密码,verbose参数等级等与业务无关的或者为了便于运维,性能跟踪的额**数;登录密码加密解密:GSQL登录密码不允许明文存储,需要密文方式保存在ETL服务器上,执行时候也需要避免出现在后台命令行中被ps指令查看到;异常处理:GSQL脚本运行出错后的异常处理功能:如重跑,告警通知等;运行日志解析:GSQL的运行日志解析:针对GSQL脚本中不同语句,不同事务的执行时间解析跟踪,错误,告警代码的解析跟踪,为性能分析提供最详细的底层数据;五、小结 本文对数据仓库构建ETL子系统进行了初步介绍,说明了当前较为主流的两种ETL子系统实现架构,比对MPPDB数据库的ETL架构进行了对比说明。最后对GaussDB(DWS)下的ETL子系统的实现要点进行了梳理,重点对etl实现的逻辑视图以及GSQL封装的功能要点进行了阐述。在今后的篇章,作者会对gsql的具体封装实现的最佳实践做个更为详细的介绍。 想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~ 原文链接:https://bbs.huaweicloud.com/blogs/265260
-
【摘要】 孙子兵法云:“谋定而后动,知止而有得”,做任何事一定要进行谋划部署,做好准备,这样才能利于这件事的成功,切不可莽撞而行。同样,GaussDB(DWS)执行查询语句也会按照预定的计划来执行,给定硬件环境的情况下,执行的快慢全凭计划的好坏,那么一条查询语句的计划是如何制定的呢,本文将为大家解读计划生成中行数估算和路径生成的奥秘。GaussDB(DWS)优化器的计划生成方法有两种,一是动态规划,二是遗传算法,前者是使用最多的方法,也是本系列文章重点介绍对象。一般来说,一条SQL语句经语法树(ParseTree)生成特定结构的查询树(QueryTree)后,从QueryTree开始,才进入计划生成的核心部分,其中有一些关键步骤:设置初始并行度(Dop)查询重写估算基表行数估算关联表(JoinRel)路径生成,生成最优Path由最优Path创建用于执行的Plan节点调整最优并行度本文主要关注3、4、5,这些步骤对一个计划生成影响比较大,其中主要涉及行数估算、路径选择方法和代价估算(或称Cost估算),Cost估算是路径选择的依据,每个算子对应一套模型,属于较为独立的部分,后续文章再讲解。Plan Hint会在3、4、5等诸多步骤中穿插干扰计划生成,其详细的介绍读者可参阅博文:GaussDB(DWS)性能调优系列实现篇六:十八般武艺Plan hint运用。先看一个简单的查询语句:select count(*) from t1 join t2 on t1.c2 = t2.c2 and t1.c1 > 100 and (t1.c3 is not null or t2.c3 is not null);GaussDB(DWS)优化器给出的执行计划如下:postgres=# explain verbose select count(*) from t1 join t2 on t1.c2 = t2.c2 and t1.c1 > 100 and (t1.c3 is not null or t2.c3 is not null); QUERY PLAN -------------------------------------------------------------------------------------------------------------- id | operation | E-rows | E-distinct | E-memory | E-width | E-costs ----+--------------------------------------------------+--------+------------+----------+---------+--------- 1 | -> Aggregate | 1 | | | 8 | 111.23 2 | -> Streaming (type: GATHER) | 4 | | | 8 | 111.23 3 | -> Aggregate | 4 | | 1MB | 8 | 101.23 4 | -> Hash Join (5,7) | 3838 | | 1MB | 0 | 98.82 5 | -> Streaming(type: REDISTRIBUTE) | 1799 | 112 | 2MB | 10 | 46.38 6 | -> Seq Scan on test.t1 | 1799 | | 1MB | 10 | 9.25 7 | -> Hash | 1001 | 25 | 16MB | 8 | 32.95 8 | -> Streaming(type: REDISTRIBUTE) | 1001 | | 2MB | 8 | 32.95 9 | -> Seq Scan on test.t2 | 1001 | | 1MB | 8 | 4.50 Predicate Information (identified by plan id) ----------------------------------------------------------------- 4 --Hash Join (5,7) Hash Cond: (t1.c2 = t2.c2) Join Filter: ((t1.c3 IS NOT NULL) OR (t2.c3 IS NOT NULL)) 6 --Seq Scan on test.t1 Filter: (t1.c1 > 100)通常一条查询语句的Plan都是从基表开始,本例中基表t1有多个过滤条件,从计划上看,部分条件下推到基表上了,部分条件没有下推,那么它的行数如何估出来的呢?我们首先从基表的行数估算开始。一、基表行数估算 如果基表上没有过滤条件或者过滤条件无法下推到基表上,那么基表的行数估算就是统计信息中显示的行数,不需要特殊处理。本节考虑下推到基表上的过滤条件,分单列和多列两种情况。1、单列过滤条件估算思想基表行数估算目前主要依赖于统计信息,统计信息是先于计划生成由Analyze触发收集的关于表的样本数据的一些统计平均信息,如t1表的部分统计信息如下:postgres=# select tablename, attname, null_frac, n_distinct, n_dndistinct, avg_width, most_common_vals, most_common_freqs from pg_stats where tablename = 't1'; tablename | attname | null_frac | n_distinct | n_dndistinct | avg_width | most_common_vals | most_common_freqs -----------+---------+-----------+------------+--------------+-----------+------------------+------------------- t1 | c1 | 0 | -.5 | -.5 | 4 | | t1 | c2 | 0 | -.25 | -.431535 | 4 | | t1 | c3 | .5 | 1 | 1 | 6 | {gauss} | {.5} t1 | c4 | .5 | 1 | 1 | 8 | {gaussdb} | {.5} (4 rows)各字段含义如下:null_frac:空值比例n_distinct:全局distinct值,取值规则:正数时代表distinct值,负数时其绝对值代表distinct值与行数的比n_dndistinct:DN1上的distinct值,取值规则与n_distinct类似avg_width:该字段的平均宽度most_common_vals:高频值列表most_common_freqs:高频值的占比列表,与most_common_vals对应从上面的统计信息可大致判断出具体的数据分布,如t1.c1列,平均宽度是4,每个数据的平均重复度是2,且没有空值,也没有哪个值占比明显高于其他值,即most_common_vals(简称MCV)为空,这个也可以理解为数据基本分布均匀,对于这些分布均匀的数据,则分配一定量的桶,按等高方式划分了这些数据,并记录了每个桶的边界,俗称直方图(Histogram),即每个桶中有等量的数据。有了这些基本信息后,基表的行数大致就可以估算了。如t1表上的过滤条件"t1.c1>100",结合t1.c1列的均匀分布特性和数据分布的具体情况:postgres=# select histogram_bounds from pg_stats where tablename = 't1' and attname = 'c1'; histogram_bounds ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------ {1,10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180,190,200,210,220,230,240,250,260,270,280,290,300,310,320,330,340,350,360,370,380,390,400,410,420,430,440,450,460,470,480,490,500,510,520,530,540,550,560,570,580,590,600,610,62 0,630,640,650,660,670,680,690,700,710,720,730,740,750,760,770,780,790,800,810,820,830,840,850,860,870,880,890,900,910,920,930,940,950,960,970,980,990,1000} (1 row)可知,t1.c1列的数据分布在1~1000之间,而每两个边界中含有的数据量是大致相同的(这里是根据样本统计的统计边界),先找到100在这个直方图中的大概位置,在这里它是某个桶的边界(有时在桶的内部),那么t1.c1>100的数据占比大约就是边界100之后的那些桶的数量的占比,这里的占比也称为选择率,即经过这个条件后,被选中的数据占比多少,因此由“t1.c >100“过滤之后的行数就可以估算出来了。以上就是估算基表行数的基本思想。一般地,有统计信息:等值条件1)对比MCV,如果满足过滤条件,则选择率(即most_common_freqs)累加;2)对Histogram数据,按distinct值个数粗略估算选择率;范围条件1)对比MCV数据,如果满足过滤条件,则选择率累加;2)对Histogram数据,按边界位置估算选择率;不等值条件:可转化为等值条件估算无统计信息:等值条件:比如过滤条件是:“substr(c3, 1, 5) = 'gauss'”,c3列有统计信息,但substr(c3, 1, 5)没有统计信息。那如何估算这个条件选择率呢?一个简单的思路是,如果substr(c3, 1, 5) 的distinct值已知的话,则可粗略假设每个distinct值的重复度一致,于是选择率也可以估算出来;在GaussDB(DWS)中,可通过设置cost_model_version=1开启表达式distinct值估算功能;范围条件:此时仅仅知道substr(c3, 1, 5)的distinct值是无法预估选择率的,对于无法估算的表达式,可通过qual_num_distinct进行设置指定相应distinct值;不等值条件:可转化为等值条件估算2. 多列过滤条件估算思想比如t1表有两个过滤条件:t1.c1 = 100 and t1.c3 = 'gauss',那么如何估算该两列的综合选择率?在GaussDB(DWS)中,一般性方法有两个:仅有单列统计信息该情况下,首先按单列统计信息计算每个过滤条件的选择率,然后选择一种方式来组合这些选择率,选择的方式可通过设置cost_param来指定。为何需要选择组合方式呢?因为实际模型中,列与列之间是有一定相关性的,有的场景中相关性比较强,有的场景则比较弱,相关性的强弱决定了最后的行数。该参数的意义和使用介绍可参考:GaussDB(DWS)性能调优系列实战篇五:十八般武艺之路径干预。有多列组合统计信息如果过滤的组合列的组合统计信息已经收集,则优化器会优先使用组合统计信息来估算行数,估算的基本思想与单列一致,即将多列组合形式上看成“单列”,然后再拿多列的统计信息来估算。比如,多列统计信息有:((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”的多列统计信息中,选取“与过滤条件的列组合的交集最大“的那个多列统计信息。对于无法匹配多列统计信息列的过滤条件,则使用单列统计信息进行估算。3. 值得注意的地方目前使用多列统计信息时,不支持范围类条件;如果有多组多列条件,则每组多列条件的选择率相乘作为整体的选择率。上面说的单列条件估算和多列条件估算,适用范围是每个过滤条件中仅有表的一列,如果一个过滤条件是多列的组合,比如 “t1.c1 < t1.c2”,那么一般而言单列统计信息是无法估算的,因为单列统计信息是相互独立的,无法确定两个独立的统计数据是否来自一行。目前多列统计信息机制也不支持基表上的过滤条件涉及多列的场景。无法下推到基表的过滤条件,则不纳入基表行数估算的考虑范畴,如上述:t1.c3 is not null or t2.c3 is not null,该条件一般称为JoinFilter,会在创建JoinRel时进行估算。如果没有统计信息可用,那就给默认选择率了。二、JoinRel行数估算 基表行数估算完,就可以进入表关联阶段的处理了。那么要关联两个表,就需要一些信息,如基表行数、关联之后的行数、关联的方式选择(也叫Path的选择,请看下一节),然后在这些方式中选择代价最小的,也称之为最佳路径。对于关联条件的估算,也有单个条件和多个条件之分,优化器需要算出所有Join条件和JoinFilter的综合选择率,然后给出估算行数,先看单个关联条件的选择率如何估算。1. 一组Join条件估算思想 与基表过滤条件估算行数类似,也是利用统计信息来估算。比如上述SQL示例中的关联条件:t1.c2 = t2.c2,先看t1.c2的统计信息:postgres=# select tablename, attname, null_frac, n_distinct, n_dndistinct, avg_width, most_common_vals, most_common_freqs from pg_stats where tablename = 't1' and attname = 'c2'; tablename | attname | null_frac | n_distinct | n_dndistinct | avg_width | most_common_vals | most_common_freqs -----------+---------+-----------+------------+--------------+-----------+------------------+------------------- t1 | c2 | 0 | -.25 | -.431535 | 4 | | (1 row)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:postgres=# select reltuples from pg_class where relname = 't1'; reltuples ----------- 2000 (1 row)于是,t1.c2的distinct是0.25 * 2000 = 500,类似地,根据统计信息,t2.c2的distinct是100:postgres=# select tablename, attname, null_frac, n_distinct, n_dndistinct from pg_stats where tablename = 't2' and attname = 'c2'; tablename | attname | null_frac | n_distinct | n_dndistinct -----------+---------+-----------+------------+-------------- t2 | c2 | 0 | 100 | -.39834 (1 row)那么,t1.c2的distinct值是否可以直接用500呢?答案是不能。因为基表t1上还有个过滤条件"t1.c1 > 100",当前关联是发生在基表过滤条件之后的,估算的distinct应该是过滤条件之后的distinct有多少,不应是原始表上有多少。那么此时可以采用各种假设模型来进行估算,比如几个简单模型:Poisson模型(假设t1.c1与t1.c2相关性很弱)或完全相关模型(假设t1.c1与t1.c2完全相关),不同模型得到的值会有差异,在本例中,"t1.c1 > 100"的选择率是 8.995000e-01,则用不同模型得到的distinct值会有差异,如下:Poisson模型(相关性弱模型):500 * (1.0 - exp(-2000 * 8.995000e-01 / 500)) = 486完全相关模型:500 * 8.995000e-01 = 450完全不相关模型:500 * (1.0 - pow(1.0 - 8.995000e-01, 2000 / 500)) = 499.9,该模型可由概率方法得到,感兴趣读者可自行尝试推导实际过滤后的distinct:500,即c2与c1列是不相关的postgres=# select count(distinct c2) from t1 where c1 > 100; count ------- 500 (1 row)估算过滤后t1.c2的distinct值,那么"t1.c2 = t2.c2"的选择率就可以估算出来了: 1 / distinct。以上是任一表没有MCV的情况,如果t1.c2和t2.c2都有MCV,那么就先比较它们的MCV,因为MCV中的值都是有明确占比的,直接累计匹配结果即可,然后再对Histogram中的值进行匹配。2. 多组Join条件估算思想 表关联含有多个Join条件时,与基表过滤条件估算类似,也有两种思路,优先尝试多列统计信息进行选择率估算。当无法使用多列统计信息时,则使用单列统计信息按照上述方法分别计算出每个Join条件的选择率。那么组合选择率的方式也由参数cost_param控制,详细参考GaussDB(DWS)性能调优系列实战篇五:十八般武艺之路径干预。另外,以下是特殊情况的选择率估算方式:如果Join列是表达式,没有统计信息的话,则优化器会尝试估算出distinct值,然后按没有MCV的方式来进行估算;Left Join/Right Join需特殊考虑以下一边补空另一边全输出的特点,以上模型进行适当的修改即可;如果关联条件是范围类的比较,比如"t1.c2 < t2.c2",则目前给默认选择率:1 / 3;3. JoinFilter的估算思想 两表关联时,如果基表上有一些无法下推的过滤条件,则一般会变成JoinFilter,即这些条件是在Join过程中进行过滤的,因此JoinFilter会影响到JoinRel的行数,但不会影响基表扫描上来的行数。严格来说,如果把JoinRel看成一个中间表的话,那么这些JoinFilter是这个中间表的过滤条件,但JoinRel还没有产生,也没有行数和统计信息,因此无法准确估算。然而一种简单近似的方法是,仍然利用基表,粗略估算出这个JoinFilter的选择率,然后放到JoinRel最终行数估算中去。三、路径生成 有了前面两节的行数估算的铺垫,就可以进入路径生成的流程了。何为路径生成?已知表关联的方式有多种(比如 NestLoop、HashJoin)、且GaussDB(DWS)的表是分布式的存储在集群中,那么两个表的关联方式可能就有多种了,而我们的目标就是,从这些给定的基表出发,按要求经过一些操作(过滤条件、关联方式和条件、聚集等等),相互组合,层层递进,最后得到我们想要的结果。这就好比从基表出发,寻求一条最佳路径,使得我们能最快得到结果,这就是我们的目的。本节我们介绍Join Path和Aggregate Path的生成。1. Join Path的生成 GaussDB(DWS)优化器选择的基本思路是动态规划,顾名思义,从某个开始状态,通过求解中间状态最优解,逐步往前演进,最后得到全局的最优计划。那么在动态规划中,总有一个变量,驱动着过程演进。在这里,这个变量就是表的个数。本节,我们以如下SQL为例进行讲解:select count(*) from t1, t2 where t1.c2 = t2.c2 and t1.c1 < 800 and exists (select c1 from t3 where t1.c1 = t3.c2 and t3.c1 > 100);该SQL语句中,有三个基表t1, t2, t3,三个表的分布键都是c1列,共有两个关联条件:t1.c2 = t2.c2, t1与t2关联t1.c1 = t3.c2, t1与t3关联为了配合分析,我们结合日志来帮助大家理解,设置如下参数,然后在执行语句:set logging_module='on(opt_join)'; set log_min_messages=debug2;第一步,如何获取t1和t2的数据首先,如何获取t1和t2的数据,比如 Seq Scan、Index Scan等,由于本例中,我们没有创建Index,那选择只有Seq Scan了。日志片段显示:我们先记住这三组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最优路径,下面就可以选择关联路径了。第二步,求解(t1, t2)关联的最优路径t1和t2两个表的分布键都是c1列,但Join列都是c2列,那么理论上的路径就有:(放在右边表示作为内表)Broadcast(t1) join t2t1 join Broadcast(t2)Broadcast(t2) join t1t2 join Broadcast(t1)Redistribute(t1) join Redistribute(t2)Redistribute(t2) join Redistribute(t1)然后每一种路径又可以搭配不同的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作为内表的路径。第三步,求解(t1, t3)关联的最优路径t1和t3表的关联条件是:t1.c1 = t3.c2,因为t1的Join列是分布键c1列,于是t1表上不需要加Redistribute;由于t1和t3的Join方式是Semi Join,外表不能Broadcast,否者可能会产生重复结果;另外还有一类Unique Path选择(即t3表去重),那么可用的候选路径大致如下: t1 semi join Redistribute(t3)Redistribute(t3) right semi join t1t1 join Unique(Redistribute(t3))Unique(Redistribute(t3)) join t1由于只有一边需要重分布且可以进行重分布,则不选Broadcast,因为相同数据量时Broadcast的代价一般要高于重分布,提前剪枝掉。再把Join方法考虑进去,于是优化器给出了最终选择:此时的最优计划是选择了内表Unique Path的路径,即t3表先去重,然后在走Inner Join过程。第四步,求解(t1,t2,t3)关联的最优路径有了前面两步的铺垫,三个表关联的思路是类似的,形式上是分解成两个表先关联,然候在与第三个表关联,实际操作上是直接取出所有两表关联的JoinRel,然后逐个增加另一个表,尝试关联,选择的方式如下:JoinRel(t1, t2) join t3:(t1, t2)->(cheapest_startup_path + cheapest_total_path) join t3->(cheapest_startup_path + cheapest_total_path)JoinRel(t1, t3) join t2:(t1, t3)->(cheapest_startup_path + cheapest_total_path) join t2->(cheapest_startup_path + cheapest_total_path)JoinRel(t2, t3) join t1:由于没有(t2, t3)关联,所以此种情况不存在每取一对内外表的Path进行Join时,也会判断是否需要重分布、是否可以去重,选择关联方式,比如JoinRel(t1, t2) join t3时,也会尝试对t3表进行去重的Path,因为这个Join本质仍然是Semi Join。下图是选择过程中产生的部分有价值的候选路径(篇幅所限,只截取了一部分):优化器在这些路径中,选出了如下的最优路径:对比实际的执行计划,二者是一样的(对比第4层HashJoin的“E-costs“是一样的):从这个过程可以大致感受到path_list有可能会发生一些膨胀,如果path_list中路径太多了,则可能会导致cheapest_total_path有多个,那么下一级的搜索空间也就会变的很大,最终会导致计划生成的耗时增加。关于Join Path的生成,作以下几点说明:Join路径的选择时,会分两个阶段计算代价,initial和final代价,initial代价快速估算了建hash表、计算hash值以及下盘的代价,当initial代价已经比path_list中某个path大时,就提前剪枝掉该路径;cheapest_total_path有多个原因:主要是考虑到多个维度下,代价很相近的路径都有可能是下一层动态规划的最佳选择,只留一个可能得不到整体最优计划;cheapest_startup_path记录了启动代价最小的一个,这也是预留了另一个维度,当查询语句需要的结果很少时,有一个启动代价很小的Path,但总代价可能比较大,这个Path有可能会成为首选;由于剪枝的原因,有些情况下,可能会提前剪枝掉某个Path,或者这个Path没有被选为cheapest_total_path或cheapest_startup_path,而这个Path是理论上最优计划的一部分,这样会导致最终的计划不是最优的,这种场景一般概率不大,如果遇到这种情况,可尝试使用Plan Hint进行调优;路径生成与集群规模大小、系统资源、统计信息、Cost估算都有紧密关系,如集群DN数影响着重分布的倾斜性和单DN的数据量,系统内存影响下盘代价,统计信息是行数和distinct值估算的第一手数据,而Cost估算模型在整个计划生成中,是选择和淘汰的关键因素,每个JoinRel的行数估算不准,都有可能影响着最终计划。因此,相同的SQL语句,在不同集群或者同样的集群不同统计信息,计划都有可能不一样,如果路径发生一些变化可通过分析Performance信息和日志来定位问题,Performance详解可以参考博文:GaussDB(DWS)的explain performance详解。如果设置了Random Plan模式,则动态规划的每一层cheapest_startup_path和cheapest_total_path都是从path_list中随机选取的,这样保证随机性。2. Aggregate Path的生成 一般而言,Aggregate Path生成是在表关联的Path生成之后,且有三个主要步骤(Unique Path的Aggregate在Join Path生成的时候就已经完成了,但也会有这三个步骤):先估算出聚集结果的行数,然后选择Path的方式,最后创建出最优Aggregate Path。前者依赖于统计信息和Cost估算模型,后者取决于前者的估算结果、集群规模和系统资源。Aggregate行数估算主要根据聚集列的distinct值来组合,我们重点关注Aggregate行数估算和最优Aggregate Path选择。2.1 Aggregate行数估算以如下SQL为例进行说明:select t1.c2, t2.c2, count(*) cnt from t1, t2 where t1.c2 = t2.c2 and t1.c1 < 500 group by t1.c2, t2.c2;该语句先是两表关联,基表上有过滤条件,然后求取两列的GROUP BY结果。这里的聚集列有两个,t1.c2和t2.c2,在看一下系统表中给出的原始信息:postgres=# select tablename, attname, null_frac, n_distinct, n_dndistinct from pg_stats where (tablename = 't1' or tablename = 't2') and attname = 'c2'; tablename | attname | null_frac | n_distinct | n_dndistinct -----------+---------+-----------+------------+-------------- t1 | c2 | 0 | -.25 | -.431535 t2 | c2 | 0 | 100 | -.39834 (2 rows)统计信息显示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模型,相同列之间用完全相关模型,则t1.c2的distinct大约是这样:distinct(t1.c2) = Poisson(d0, ratio1, nRows) * ratio2其中d0表示基表中原始distinct,ratio1代表使用Poisson模型的Ratio,ratio2代表使用完全相关模型的Ratio,nRows是基表行数。如果需要定位分析问题,这些Ratio可以从日志中查阅,如下设置后在运行SQL语句:set logging_module='on(opt_card)'; set log_min_messages=debug3;本例中,我们从日志中可以看到t1表上的两个Ratio:在看t2.c2,这一列原始distinct是100,而从上面日志中可以看出t2表的数据全匹配上了(没有Ratio),那么Join完t2.c2的distinct也是100。此时不能直接组合t1.c2和t2.c2,因为"t1.c2 = t2.c2“暗含了这两个列的值是一样的,那就是说它们等价,于是只需考虑Min(distinct(t1.c2), distinct(t2.c2))即可,下图是Performance给出的实际和估算行数:postgres=# explain performance select t1.c2, t2.c2, count(*) cnt from t1, t2 where t1.c2 = t2.c2 and t1.c1 < 500 group by t1.c2, t2.c2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------ id | operation | A-time | A-rows | E-rows | E-distinct | Peak Memory | E-memory | A-width | E-width | E-costs ----+-----------------------------------------------+------------------+--------+--------+------------+----------------+----------+---------+---------+--------- 1 | -> Streaming (type: GATHER) | 48.500 | 99 | 100 | | 80KB | | | 16 | 89.29 2 | -> HashAggregate | [38.286, 40.353] | 99 | 100 | | [28KB, 31KB] | 16MB | [24,24] | 16 | 79.29 3 | -> Hash Join (4,6) | [37.793, 39.920] | 1980 | 2132 | | [6KB, 6KB] | 1MB | | 8 | 75.04 4 | -> Streaming(type: REDISTRIBUTE) | [0.247, 0.549] | 1001 | 1001 | 25 | [53KB, 53KB] | 2MB | | 4 | 32.95 5 | -> Seq Scan on test.t2 | [0.157, 0.293] | 1001 | 1001 | | [12KB, 12KB] | 1MB | | 4 | 4.50 6 | -> Hash | [36.764, 38.997] | 998 | 1000 | 62 | [291KB, 291KB] | 16MB | [20,20] | 4 | 29.88 7 | -> Streaming(type: REDISTRIBUTE) | [36.220, 38.431] | 998 | 999 | | [53KB, 61KB] | 2MB | | 4 | 29.88 8 | -> Seq Scan on test.t1 | [0.413, 0.433] | 998 | 999 | | [14KB, 14KB] | 1MB | | 4 | 9.25 2.2 Aggregrate Path生成有了聚集行数,则可以根据资源情况,灵活选择聚集方式。Aggregate方式主要有以下三种:Aggregate + Gather (+ Aggregate)Redistribute + Aggregate (+Gather)Aggregate + Redistribute + Aggregate (+Gather)括号中的表示可能没有这一步,视具体情况而定。这些聚集方式可以理解成,两表关联时选两边Redistribute还是选一边Broadcast。优化器拿到聚集的最终行数后,会尝试每种聚集方式,并计算相应的代价,选择最优的方式,最终生成路径。这里有两层Aggregate时,最后一层就是最终聚集行数,而第一层聚集行数是根据Poisson模型推算的。Aggregate方式选择默认由优化器根据代价选择,用户也可以通过参数best_agg_plan指定。三类聚集方式大致适用范围如下:第一种,直接聚集后行数不太大,一般是DN聚集,CN收集,有时CN需进行二次聚集第二种,需要重分布且直接聚集后行数未明显减少第三种,需要重分布且直接聚集后行数减少明显,重分布之后,行数又可以减少,一般是DN聚集、重分布、再聚集,俗称双层Aggregate四、结束语 本文着眼于计划生成的核心步骤,从行数估算、到Join Path的生成、再到Aggregate Path的生成,介绍了其中最简单过程的基本原理。而实际的处理方法远远比描述的要复杂,需要考虑的情况很多,比如多组选择率如何组合最优、分布键怎么选、出现倾斜如何处理、内存用多少等等。权衡整个计划生成过程,有时也不得不有所舍,这样才能有所得,而有时计划的一点劣势也可以忽略或者通过其他能力弥补上来,比如SMP开启后,并行的效果会淡化一些计划上的缺陷。总而言之,计划生成是一项复杂而细致的工作,生成全局最优计划需要持续的发现问题和优化,后续博文我们将继续探讨计划生成的秘密。想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~原文链接:https://bbs.huaweicloud.com/blogs/263792
-
GaussDB(DWS)是MPP并行架构,若表的数据存在倾斜情况,会引起一系列性能问题,影响用户体验,严重时可能会引起系统故障。因此能快速获取倾斜的表并整改是GaussDB(DWS)运维管理人员比较关注的事情。需求描述 GaussDB(DWS)自身提供pgxc_get_table_skewness视图来查询倾斜情况,但实际实践过程中,该视图存在性能问题,且该视图的倾斜率计算有问题。实践过程中,该视图获取的某个表的倾斜率在不高的情况下(例如0.03),但实际上该表是存在倾斜情况的。 同时在很多时候我们需要获取一个schema下所有表的倾斜率,以排查倾斜问题,pgxc_get_table_skewness在产品文档中也描述是一个性能较差的视图。 因此项目实践过程中急需一个性能好且能表达倾斜情况的函数或视图。设计思路 GaussDB(DWS)有获取每个DN的空间大小函数table_distribution,通过该函数,我们能快速获取每个DN的大小,同时可以根据每个DN的大小,来获取表的倾斜情况:skewness = (max(dnsize) - avg(dnsize))*100/max(dnsize) 该倾斜率公式计算表的最大DN空间大小与平均DN空间大小的占比,能准确反映倾斜率,乘100为表现百分比。实现过程 根据倾斜公式,我们得出以下SQL,该SQL能快速获取schema所有表的倾斜情况,下面以public为例:select schemaname,tablename,sum(dnsize)/1024^3 dnsize_gb,(max(dnsize) - avg(dnsize))*100/nullif(max(dnsize),0) skewness_factor from ( select schemaname ,tablename ,(regexp_split_to_array(tbl_dis,'[\,\(\)]+'))[4]::bigint as vprocname ,(regexp_split_to_array(tbl_dis,'[\,\(\)]+'))[5]::bigint as dnsize from ( select nspname as schemaname ,relname as tablename ,table_distribution(nspname,relname)::text as tbl_dis from pg_class a inner join pg_namespace b on a.relnamespace = b.oid and a.relkind = 'r' and b.oid not in (100) ) ) where schemaname= 'public' group by 1,2 order by 3 desc; 结果样例如下,通过例子,可以看出来,test13这个表2GB,且发生严重的倾斜97%,同时store_sales1一个70GB的大表也存在倾斜情况58%与GaussDB(DWS)的pgxc_get_table_skewness视图结果比对 使用GaussDB(DWS) 的系统视图pgxc_get_table_skewness,比较难看出来store_sales1存在倾斜情况。 此处我们使用的是系统视图pgxc_get_table_skewness获取:select * from PGXC_GET_TABLE_SKEWNESS where schemaname = 'public' and tablename in ('store_sales1','test13'); 从结果上看,skewratio字段,test13表能看出来存在严重倾斜情况,而store_sales1的skewratio值只有0.031,看不出来存在倾斜情况。但事实上该表是存在一定倾斜的 我们通过table_skewness看每个DN的数据分布验证,发现store_sales1的确存在一定倾斜。总结: GaussDB(DWS)的倾斜率获取视图pgxc_get_table_skewness的结果,虽能反映严重倾斜的表,但存在倾斜的大表则比较难看出来。同时该函数存在一定的性能问题,较多表的情况下基本执行不出来。 本文提供的倾斜率获取办法能比较准确反映表的倾斜情况且能叫快速获取整schema所有的表的倾斜率方法;该方法在测试过程中,数据量越大,表越多,执行的时间会越慢,测试一个schema约3800张表,共40TB左右的数据,在5分钟左右获取所有表的空间大小与倾斜率。 但本文提供的方法只能对单个schema操作,对整个数据库获取表空间大小与倾斜率,实测无法执行成功。若对时效性不要求的话,可以每天固定一个时间,已跑批的形式,获取一个库的所有表清单,使用table_distribution函数,一次一个表地获取表的空间信息,使用多并发执行,这样的方式能在一定时间内将所有表的空间情况执行完成。 例如:对整库有10万张表的情况,可以使用100个并发同时执行 insert into table_size_info select * from table_distribution('schema.table'); 这样的方式将10万张表的DN空间信息获取完成,然后使用本文的公式汇总获取每个表的倾斜率与空间总大小。 想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料~原文链接:https://bbs.huaweicloud.com/blogs/266743
-
方法:SELECT c.ev_class::regclass::varchar AS objname, pc.oid::regclass::varchar AS refobjnameFROM pg_depend a,pg_depend b,pg_class pc,pg_rewrite c,pg_catalog.pg_namespace nWHERE a.refclassid=1259AND a.classid=2618AND b.deptype='i'AND a.objid=b.objidAND a.classid=b.classidAND a.refclassid=b.refclassidAND a.refobjid<>b.refobjidAND pc.oid=a.refobjidAND c.oid=b.objidAND n.oid = pc.relnamespaceAND (a.objid>=16384 or a.refobjid>=16384)AND n.nspname ='<schemaname>'GROUP BY c.ev_class,pc.oid;eg:
-
【功能模块】参数【操作步骤&问题现象】GaussDB A 6.5.1 , 8.0 版本(线下)查看 timezone 都是设置的 PRC , DWS的timezone 参数默认是 UTC , 导致同一个应用写入不同集群的时间字段上的值不同 。 虽然看到很多文章讲到和客户端有关系,一般用户极少会在客户端设置timezone之类的东西,所有可以假设不考虑客户端 。 如果需要得到正确的时间值, 是否需要更改 DWS 的默认 timezone ?? 【截图信息】DWS 默认参数: 北京时间下午 15:47, DWS 上查询的时间 8.0 版本GaussDB A 线下集群默认timezone 是 PRC (北京时间下午 14:43 ) . 【日志信息】(可选,上传日志内容或者附件)
-
提个小建议哈希望今后的免费试用活动能够为大家制定一个学习小目标,比如出些题目由我们来完成,或者简单的答卷形式也好,答完可以有结业证书等,诸如此类让活动有一定方向。
每天都要有进步
发表于2021-06-01 10:43:55
2021-06-01 10:43:55
最后回复
Select*fromMacchiato
2021-06-01 17:07:56
2223 2 -
【操作步骤&问题现象】Hetu连接GaussdDB 数据源在多表关联场景下速度快于本身GaussDB 1.在默认不添加catlog自定义配置情况下描述下为什么?2.在添加parallel-read-enabled,postgresql.allow.filter-pushdown自定义配置下描述为什么?
-
产品文档有如下的描述:在初始数据库postgres中创建用户时,系统会为新用户创建一个同名schema。在其他数据库中,若需要同名schema,则需要用户手动创建。无论是在初始数据库还是其它数据库,创建一个新用户时都会自动创建一个与之同名的schema。还是我使用的方法不对?如何限制创建一个新用户时自动创建一个与之同名的schema?
-
购买了一个月的DWS服务资源,现不使用,已删除DWS集群,目前在待续费中有3个提示需要续费,如何操作才会让其不产生费用,清除或释放资源。
-
问题是:创建一个用户时会自动创建一个同名的schema,如何限制让其不会自动创建一个同名的schema?
-
GaussDB(DWS)两级用户权限管理实践一、 场景描述 在项目交付中,经常会用到两级或者多级用户权限管理,例如系统用户分为省和市两级,省级用户包括1名省级管理员和N名省级普通用户,市级用户包括多名市级管理员(每个地市设置1名市级管理员)和N名普通用户。省级管理员对接管理省级用户和市级管理员用户,市级管理员对接管理市级用户,并进行分级用户和权限管理。例如针对如下场景:结合用户场景需求,梳理用户组织架构。【说明】(1)系统管理员是数据库自带的用户,作为一个技术用户和数据库日常维护用户,一般不在日常应用开发中使用。(2)省级管理员设置1名,可以作为应用开发和管理的全局用户,由省级管理员创建其他地市管理员或者省级普通用户。通常来说,省级管理员只负责创建地市的管理员,之后,由地市管理员管理地市的普通用户,省级管理员也可以创建省级的普通用户。(3)地市管理员,作为某地市的管理员,可以管理本地市的所有用户。(4)普通用户,日常应用开发或者查询的用户。二、 两级用户权限管理实践由于GaussDB(DWS) 8.0版本暂不支持with grant option(后续版本已支持,整体方案实现步骤会相对简单),结合用户场景需求和组织架构,使用常规赋权语句设计两级用户权限管理方案如下:省级范围由省级管理员用户统一管理,分解工作如下:省级管理员创建和管理省级用户组和省级普通用户。省级管理员将省级schema权限授予省级用户组。省级管理员创建和管理省级schema。省级管理员创建市级schema,并将市级schema的owner修改为市级管理员。省级管理员将省级schema权限授予市级用户组(部分市级用户需要查询使用省级schema相关表信息)。省级管理员创建市级管理员用户,并赋予create role权限。市级范围由市级管理员用户统一管理,分解工作如下:市级管理员创建和管理市级用户组和市级普通用户。市级管理员将市级schema权限授予市级用户组。市级管理员管理市级schema。三、 授权方案验证1、 用户场景假设角色假设角色名称系统管理员Ruby省级管理员Province_adminA地市管理员City_A_adminB地市管理员City_B_admin省级普通用户Province_01A地市普通用户01City_A_user01A地市普通用户02City_A_user02B地市普通用户01City_B_user01省级用户组Province_role01市级用户组CityA_role2、 用户与schema的关系schema用户schema与用户的关系province_schema省级管理员schema的owner省级普通用户01普通用户city_a_schema地市A管理员schema的owner地市A普通用户01普通用户地市A普通用户02普通用户city_b_schema地市B管理员schema的owner地市B普通用户01普通用户 3、 权限赋予方法步骤1:首先通过系统管理员(Ruby用户),创建省级管理员province_admin,省级管理员拥有sysadmin的权限。--i. 以系统管理员的用户登录(数据库后台)gsql -d postgres -p 8000 -ar--ii. 创建省级管理员create user province_admin with sysadmin identified by 'test@123'; 步骤2:省级管理员相关账号和权限配置命令。--i. 以省级管理员用户登录(或通过DataStudio工具前台登录)gsql -d postgres -p 8000 -ar -U province_admin -W test@123--ii. 创建地市A的管理员用户create user city_a_admin identified by 'test@123';--iii. 创建地市B的管理员用户create user city_b_admin identified by 'test@123';--iv. 赋予地市A管理员创建用户的权限alter user city_a_admin createrole;--v. 赋予地市B管理员创建用户的权限alter user city_b_admin createrole;--vi. 创建省级普通用户(注意,省级普通用户不需要createrole的权限)create user province_user01 identified by 'test@123';--vii 创建省级用户组,并将省级用户加入用户组;create role province_role01 identified by 'Bigdata123@';grant province_role01 to province_user01; 步骤3:地市管理员相关账号和权限配置命令。注意,地市A管理员创建地市A的普通用户,不需要给普通用户赋予createrole的权限。--i. 以地市A的管理员用户登录(或通过DataStudio工具前台登录)gsql -d postgres -p 8000 -ar -U city_a_admin -W test@123--ii. 创建地市A的普通用户create user city_a_user01 identified by 'test@123';create user city_a_user02 identified by 'test@123';--iii 创建市级用户组,并将市级用户加入用户组;create role citya_role identified by 'Bigdata123@';grant citya_role to city_a_user01;grant citya_role to city_a_user02;地市B管理员创建地市B的普通用户(不需要给普通用户赋予createrole的权限)。--i. 以地市B的管理员用户登录(或通过DataStudio工具前台登录)gsql -d postgres -p 8000 -ar -U city_b_admin -W test@123--ii. 创建地市B的普通用户create user city_b_user01 identified by 'test@123';--iii 创建市级用户组,并将市级用户加入用户组;create role cityb_role identified by 'Bigdata123@';grant cityb_role to city_b_user01; 步骤4:由省级管理员创建三个schema,分别为province_schema,city_a_schema,city_b_schema。并将city_a_schema和city_b_schema的owner分别赋予city_a_admin,city_b_admin。--i. 以省级管理员用户登录(或通过DataStudio工具前台登录)gsql -d postgres -p 8000 -ar -U province_admin -W test@123--ii. 创建省级的schema并赋予其拥有者为省级管理员create schema province_schema authorization province_admin;--iii. 创建地市A的schema并赋予其拥有者为地市A管理员create schema city_a_schema;alter schema city_a_schema owner to city_a_admin;--iv. 创建地市B的schema并赋予其拥有者为地市B管理员create schema city_b_schema;alter schema city_b_schema owner to city_b_admin; 步骤5:以省级管理员用户province_admin登录,赋予地市A用户组使用province_schema的权限。--i. 以省级管理员登录(或通过DataStudio工具前台登录)gsql -d postgres -p 8000 -ar -U province_admin -W test@123--ii 设置search_path= province_schemaset search_path= province_schema;--iii. 赋予地市A用户组使用省级province_schema的权限grant usage on schema province_schema to citya_role ;grant select on all tables in schema province_schema to citya_role; 步骤6:以地市A管理员用户city_a_admin登录,赋予地市A用户组使用city_a_schema的权限。--i. 以地市A管理员登录(或通过DataStudio工具前台登录)gsql -d postgres -p 8000 -ar -U city_a_admin -W test@123--ii 设置search_path=city_a_schemaset search_path=city_a_schema;--iii. 赋予地市A用户组使用市级city_a_schema的权限grant usage on schema city_a_schema to citya_role;grant select on all tables in schema city_a_schema to citya_role;步骤7:以市级普通用户city_a_user01用户登录,验证是否拥有查询province_schema 和 city_a_schema的权限。--i. 以city_a_user01的用户登录(或通过DataStudio工具前台登录)gsql -d postgres -p 8000 -ar -U city_a_user01 -W test@123--ii. 设置搜索路径为省级schema。set search_path= province_schema;--iii. 确认是否有省级schema的查询权限select * from tbl_a;--iv. 设置搜索路径为市级schema;set search_path=city_a_schema;--v. 确认是否有市级schema的查询权限select * from tbl_a;4、 权限回收方法--i 将省级用户从省级用户组剔除;revoke province_role01 from province_user01;--ii 将市级用户从市级用户组剔除;revoke citya_role from city_a_user01;
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签