以下章节中包含的示例展示了如何设计投影以优化 GROUP BY
查询的性能。
GROUP BY 查询
1 - GROUP BY 实施选项
Vertica 将使用以下算法之一来实施查询 GROUP BY 子句:GROUPBY PIPELINED 或 GROUPBY HASH。这两种算法会返回相同的结果。对于返回少量不同组(通常每个节点一千个)的查询,两者的性能基本相似。
您可以使用 EXPLAIN 来确定查询优化器为给定查询选择的算法。以下条件通常决定选择哪种算法:
-
GROUPBY PIPELINED 要求在投影的 ORDER BY 子句中指定所有 GROUP BY 数据。有关详细信息,请参阅下面的 GROUPBY PIPELINED 要求。
由于 GROUPBY PIPELINED 只需在内存中保留当前组数据,因此相比 GROUPBY HASH,该算法通常需要的内存更少且执行速度更快。对于聚合大量不同组的查询,性能改进尤为显著。
-
GROUPBY HASH 将用于任何不符合 GROUPBY PIPELINED 排序顺序要求的查询。在这种情况下,Vertica 必须先对各个 GROUP BY 列构建哈希表,然后才能开始对数据进行分组。
GROUPBY PIPELINED 要求
通过确保查询及其投影之一符合 GROUPBY PIPELINED 要求,可以启用 GROUPBY PIPELINED 算法。以下条件适用于 GROUPBY PIPELINED。如果查询不满足任何条件,则 Vertica 使用 GROUPBY HASH。
后续所有示例均假定使用此架构:
CREATE TABLE sortopt (
a INT NOT NULL,
b INT NOT NULL,
c INT,
d INT
);
CREATE PROJECTION sortopt_p (
a_proj,
b_proj,
c_proj,
d_proj )
AS SELECT * FROM sortopt
ORDER BY a,b,c
UNSEGMENTED ALL NODES;
INSERT INTO sortopt VALUES(5,2,13,84);
INSERT INTO sortopt VALUES(14,22,8,115);
INSERT INTO sortopt VALUES(79,9,401,33);
条件 1
所有 GROUP BY 列也包含在投影 ORDER BY 子句中。例如:
条件 2
如果查询的 GROUP BY 子句比投影的 ORDER BY 子句拥有的列数少,则 GROUP BY 列必须:
-
是连续的 ORDER BY 列的子集。
-
包含第一个 ORDER BY 列。
例如:
条件 3
如果查询的 GROUP BY 列不是先出现在投影的 GROUP BY 子句中,则在查询的 GROUP BY 子句中缺失的任何先出现的投影排序列必须在查询的 WHERE 子句中显示为单列恒定相等谓词。
例如:
控制选择的 GROUPBY 算法
通常最好让 Vertica 来确定最适合用于给定查询的 GROUP BY 算法。有时,您可能希望使用某种算法,而不是另一种算法。在这种情况下,您可以使用 GBYTYPE 提示来限定 GROUP BY 子句:
GROUP BY /*+ GBYTYPE( HASH | PIPE ) */
例如,指定以下查询后,查询优化器将使用 GROUPBY PIPELINED 算法:
=> EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY a,b;
------------------------------
QUERY PLAN DESCRIPTION:
------------------------------
EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY a,b;
Access Path:
+-GROUPBY PIPELINED [Cost: 11, Rows: 3 (NO STATISTICS)] (PATH ID: 1)
| Aggregates: sum(sortopt.a)
| Group By: sortopt.a, sortopt.b
...
您可以使用 GBYTYPE 提示来强制查询优化器使用 GROUPBY HASH 算法:
=> EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY /*+GBYTYPE(HASH) */ a,b;
------------------------------
QUERY PLAN DESCRIPTION:
------------------------------
EXPLAIN SELECT SUM(a) FROM sortopt GROUP BY /*+GBYTYPE(HASH) */ a,b;
Access Path:
+-GROUPBY HASH (LOCAL RESEGMENT GROUPS) [Cost: 11, Rows: 3 (NO STATISTICS)] (PATH ID: 1)
| Aggregates: sum(sortopt.a)
| Group By: sortopt.a, sortopt.b
...
仅当查询及其投影之一符合 GROUPBY PIPELINED 要求时,GBYTYPE 提示才能指定 PIPE(GROUPBY PIPELINED 算法)实参。否则,Vertica 会发出警告并使用 GROUPBY HASH。
例如,以下查询不能使用 GROUPBY PIPELINED 算法,因为 GROUP BY 列 {b,c
} 不包含投影的第一个 ORDER BY 列 a
:
=> SELECT SUM(a) FROM sortopt GROUP BY /*+GBYTYPE(PIPE) */ b,c;
WARNING 7765: Cannot apply Group By Pipe algorithm. Proceeding with Group By Hash and hint will be ignored
SUM
-----
79
14
5
(3 rows)
2 - 使用投影设计避免在 GROUP BY 优化期间重新分段
要计算包含 GROUP BY
子句的查询的正确结果,Vertica 必须确保 GROUP BY
表达式中具有相同值的所有行最终都会在相同节点上以便于最终计算。如果投影设计已经确保数据按 GROUP BY
列进行分段,那么运行时则不需要重新分段。
要避免重新分段,GROUP BY
子句必须包含投影的所有分段列,但是它也可以包含其他列。
当查询包括 GROUP BY
子句和联接时,如果联接取决于以下示例所示的 GROUP BY
的结果,那么 Vertica 会首先执行 GROUP BY
:
=> EXPLAIN SELECT * FROM (SELECT b from foo GROUP BY b) AS F, foo WHERE foo.a = F.b;
Access Path:
+-JOIN MERGEJOIN(inputs presorted) [Cost: 649, Rows: 10K (NO STATISTICS)] (PATH ID: 1)
| Join Cond: (foo.a = F.b)
| Materialize at Output: foo.b
| Execute on: All Nodes
| +-- Outer -> STORAGE ACCESS for foo [Cost: 202, Rows: 10K (NO STATISTICS)] (PATH ID: 2)
| | Projection: public.foo_super
| | Materialize: foo.a
| | Execute on: All Nodes
| | Runtime Filter: (SIP1(MergeJoin): foo.a)
| +-- Inner -> SELECT [Cost: 245, Rows: 10K (NO STATISTICS)] (PATH ID: 3)
| | Execute on: All Nodes
| | +---> GROUPBY HASH (SORT OUTPUT) (GLOBAL RESEGMENT GROUPS) (LOCAL RESEGMENT GROUPS) [Cost: 245, Rows: 10K (NO STATISTICS)] (PATH ID:
4)
| | | Group By: foo.b
| | | Execute on: All Nodes
| | | +---> STORAGE ACCESS for foo [Cost: 202, Rows: 10K (NO STATISTICS)] (PATH ID: 5)
| | | | Projection: public.foo_super
| | | | Materialize: foo.b
| | | | Execute on: All Nodes
如果联接操作的结果是 GROUP BY
子句的输入,Vertica 会首先执行联接,如以下示例所示。这些中间结果的分段可能不会与查询的 GROUP BY
子句保持一致,从而导致运行时要重新分段。
=> EXPLAIN SELECT * FROM foo AS F, foo WHERE foo.a = F.b GROUP BY 1,2,3,4;
Access Path:
+-GROUPBY HASH (LOCAL RESEGMENT GROUPS) [Cost: 869, Rows: 10K (NO STATISTICS)] (PATH ID: 1)
| Group By: F.a, F.b, foo.a, foo.b
| Execute on: All Nodes
| +---> JOIN HASH [Cost: 853, Rows: 10K (NO STATISTICS)] (PATH ID: 2) Outer (RESEGMENT)(LOCAL ROUND ROBIN)
| | Join Cond: (foo.a = F.b)
| | Execute on: All Nodes
| | +-- Outer -> STORAGE ACCESS for F [Cost: 403, Rows: 10K (NO STATISTICS)] (PUSHED GROUPING) (PATH ID: 3)
| | | Projection: public.foo_super
| | | Materialize: F.a, F.b
| | | Execute on: All Nodes
| | +-- Inner -> STORAGE ACCESS for foo [Cost: 403, Rows: 10K (NO STATISTICS)] (PATH ID: 4)
| | | Projection: public.foo_super
| | | Materialize: foo.a, foo.b
| | | Execute on: All Nodes
如果查询不包括联接,GROUP BY
子句会使用现有数据库投影进行处理。
示例
假设有以下投影:
CREATE PROJECTION ... SEGMENTED BY HASH(a,b) ALL NODES
下表说明了运行时是否会发生重新分段以及相关原因。
要确定在 GROUP BY
查询期间是否会发生重新分段,请查看
EXPLAIN
生成的查询计划。
例如,以下计划使用了 GROUPBY PIPELINED
排序优化,并需要重新分段以执行 GROUP BY
计算:
+-GROUPBY PIPELINEDRESEGMENT GROUPS[Cost: 194, Rows: 10K (NO STATISTICS)] (PATH ID: 1)
以下计划使用了 GROUPBY PIPELINED
排序优化,但是不需要重新分段:
+-GROUPBY PIPELINED [Cost: 459, Rows: 10K (NO STATISTICS)] (PATH ID: 1)