语句下推调优
语句下推介绍
目前,GaussDB优化器在分布式框架下制定语句的执行策略时,有三种执行计划方式:生成下推语句计划、生成分布式执行计划、生成发送语句的分布式执行计划。
- 下推语句计划:指直接将完整的查询语句从CN发送到DN进行执行,然后将执行结果返回给CN。
- 分布式执行计划:指CN对查询语句进行编译和优化,生成计划树,再将计划树发送给DN进行执行,并在执行完毕后返回结果到CN。
- 发送语句的分布式执行计划:上述两种方式都不可行时,将可下推的查询部分组成查询语句(多为基表扫描语句)下推到DN进行执行,获取中间结果到CN,然后在CN执行剩下的部分。
在第3种策略中,要将大量中间结果从DN发送到CN,并且要在CN运行不能下推的部分语句,会导致CN成为性能瓶颈(带宽、存储、计算等)。在进行性能调优的时候,应尽量避免只能选择第3种策略的查询语句。
执行语句不能下推是因为语句中含有不支持下推的函数或者不支持下推的语法。一般都可以通过等价改写规避执行计划不能下推的问题。
语句下推典型场景
在GaussDB优化器中如果想要支持语句下推需要将GUC参数enable_fast_query_shipping设置为on即可。通常而言explain语句后没有显示具体的执行计划算子,执行计划中关键字“Data Node Scan on”出现在第一行(不包含计划格式)则说明语句已下推给DN去执行。下面从多个维度场景介绍语句下推及其支持的范围。
- 单表查询语句下推
在分布式数据库中对于单表查询而言,当前语句是否可以下推需要判断CN是否要进一步参与计算而不是简单收集数据。如果CN要进一步对DN结果进行计算则语句不可下推。通常带有agg、windows function、limit/offset、sort、distinct等关键字都不可下推。
- 可下推:简单查询,无需在CN进一步计算则可以下推。
gaussdb=# explain select * from t where c1 > 1; QUERY PLAN ---------------------------------------------------------------------------- Data Node Scan on "__REMOTE_FQS_QUERY__" (cost=0.00..0.00 rows=0 width=0) Node/s: All datanodes (2 rows) - 不可下推:带有limit子句,对于CN而言不能简单发语句给DN并收集数据,明显与limit语义不符。
gaussdb=# explain select * from t limit 1; QUERY PLAN ------------------------------------------------------------------------------------- Limit (cost=0.00..0.00 rows=1 width=12) -> Data Node Scan on "__REMOTE_LIMIT_QUERY__" (cost=0.00..0.00 rows=1 width=12) Node/s: All datanodes (3 rows) - 不可下推:带有聚集函数CN不能简单下推语句,而应该对从DN收集结果进一步聚集运算处理。
gaussdb=# explain select sum(c1), count(*) from t; QUERY PLAN ------------------------------------------------------------------------------------- Aggregate (cost=0.10..0.11 rows=1 width=20) -> Data Node Scan on "__REMOTE_GROUP_QUERY__" (cost=0.00..0.00 rows=20 width=4) Node/s: All datanodes (3 rows)
- 可下推:简单查询,无需在CN进一步计算则可以下推。
- 多表查询语句下推
多表查询场景下语句能否下推通常与join条件以及分布列有关,即如果join条件与表分布列匹配得上则可下推,否则无法下推。对于复制表来说通常可以下推。
- 创建两个hash分布表。
gaussdb=# create table t(c1 int, c2 int, c3 int)distribute by hash(c1); CREATE TABLE gaussdb=# create table t1(c1 int, c2 int, c3 int)distribute by hash(c1); CREATE TABLE
- 可下推:join条件满足两个表hash分布列属性。
gaussdb=# explain select * from t1 join t on t.c1 = t1.c1; QUERY PLAN ---------------------------------------------------------------------------- Data Node Scan on "__REMOTE_FQS_QUERY__" (cost=0.00..0.00 rows=0 width=0) Node/s: All datanodes (2 rows) - 不可下推:join条件不满足hash分布列属性,即t1.c2不是t1表的分布列。
gaussdb=# explain select * from t1 join t on t.c1 = t1.c2; QUERY PLAN -------------------------------------------------------------------------------------------- Hash Join (cost=0.25..0.53 rows=20 width=24) Hash Cond: (t1.c2 = t.c1) -> Data Node Scan on t1 "_REMOTE_TABLE_QUERY_" (cost=0.00..0.00 rows=20 width=12) Node/s: All datanodes -> Hash (cost=0.00..0.00 rows=20 width=12) -> Data Node Scan on t "_REMOTE_TABLE_QUERY_" (cost=0.00..0.00 rows=20 width=12) Node/s: All datanodes (7 rows) - 删除两个hash分布表。
gaussdb=# DROP TABLE t; DROP TABLE gaussdb=# DROP TABLE t1; DROP TABLE
- 创建两个hash分布表。
- 集合操作、CTE下推
集合操作场景语句能否下推,与左右子查询是否下推有关,规则如下:
- UNION ALL左右分支都能下推且左右分支分布的DN相同,整个UNION ALL才能下推。
- UNION/INTERSECT/INTERSECT ALL/EXCEPT/EXCEPT ALL左右分支都能下推到相同的单个节点,整个集合操作才能下推。
--测试环境包含1个CN和6个DN --创建NODEGROUP,NODEGROUP中的DN名字可以通过语句SELECT node_name FROM PGXC_NODE WHERE node_type = 'D'查询,查询的结果按需替换CREATE NODE GROUP语句中WITH后的DN名。 gaussdb=# CREATE NODE GROUP ng WITH(datanode1, datanode2, datanode3, datanode4, datanode5, datanode6); CREATE NODE GROUP --建表 gaussdb=# CREATE TABLE t1(a int, b int, c int) DISTRIBUTE BY HASH(a) TO GROUP ng; CREATE TABLE gaussdb=# CREATE TABLE t2(a int, b int, c int) DISTRIBUTE BY HASH(a) TO GROUP ng; CREATE TABLE --UNION ALL两边都可以下推,UNION ALL可以下推 gaussdb=# EXPLAIN (COSTS OFF) SELECT * FROM t1 UNION ALL SELECT * FROM t2; QUERY PLAN ------------------------- Data Node Scan Node/s: All datanodes (2 rows) --union两边都可以下推,但不是单DN,所以不能下推 gaussdb=# EXPLAIN (COSTS OFF) SELECT * FROM t1 UNION SELECT * FROM t2; QUERY PLAN ---------------------------------------- Streaming (type: GATHER) Node/s: All datanodes -> HashAggregate Group By Key: t1.a, t1.b, t1.c -> Append -> Seq Scan on t1 -> Seq Scan on t2 (7 rows) --UNION两边都可以下推,且是同一个DN,所以能下推 gaussdb=# EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE a = 1 UNION SELECT * FROM t2 WHERE a = 1; QUERY PLAN -------------------------- Data Node Scan Node/s: (ng) datanode5 (2 rows) gaussdb=# EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE a = 1; QUERY PLAN -------------------------- Data Node Scan Node/s: (ng) datanode5 (2 rows) gaussdb=# EXPLAIN (COSTS OFF) SELECT * FROM t2 WHERE a = 3; QUERY PLAN -------------------------- Data Node Scan Node/s: (ng) datanode4 (2 rows) --union两边都可以下推,但不是同一个DN,所以不能下推 gaussdb=# EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE a = 1 UNION SELECT * FROM t2 WHERE a = 3; QUERY PLAN ------------------------------------------- Streaming (type: GATHER) Node/s: (GenGroup) datanode4, datanode5 -> Unique -> Sort Sort Key: t1.a, t1.b, t1.c -> Append -> Seq Scan on t1 Filter: (a = 1) -> Seq Scan on t2 Filter: (a = 3) (10 rows)
CTE场景语句能否下推,与CTE中的语句是否能够下推有关,规则如下:
- 对于CTE,只有CTE中的语句能够下推,整个CTE才能下推。
- 对于Recursive CTE,由于递归部分会自引用CTE,在判断递归部分能否下推时,自引用的CTE的下推信息取决于非递归部分下推信息,即非递归部分的语句可以当做自引用CTE的子查询。
- 对于Recursive CTE,在满足CTE中的语句能下推的条件下,语句中递归部分和非递归部分都可以下推到相同的DN,整个Recursive CTE才能下推。
--CTE中语句能下推,CTE可以下推 gaussdb=# EXPLAIN (COSTS OFF) WITH cte AS (SELECT * FROM t1) SELECT * FROM cte; QUERY PLAN ------------------------- Data Node Scan Node/s: All datanodes (2 rows) --CTE中语句能下推单个DN,CTE可以下推单个DN gaussdb=# EXPLAIN (COSTS OFF) WITH cte AS (SELECT * FROM t1 WHERE a = 1) SELECT * FROM cte; QUERY PLAN -------------------------- Data Node Scan Node/s: (ng) datanode5 (2 rows) --CTE中语句不能下推,CTE不能下推 gaussdb=# EXPLAIN (COSTS OFF) WITH cte AS (SELECT * FROM t1 ORDER BY a ) SELECT * FROM cte; QUERY PLAN ---------------------------- Streaming (type: GATHER) Node/s: All datanodes -> Sort Sort Key: t1.a -> Seq Scan on t1 (5 rows) --Recursive CTE中语句能下推,且递归部分和非递归部分都可以下推到相同的多个DN,Recursive CTE可以下推多个DN gaussdb=# EXPLAIN