南大通用GBase 8a做distinct、group和join时避免严重数据倾斜导致的性能问题

在执行distinct、group、join是由多个列参数时,如果投影列里没有Hash分布列,则会选择一个列做hash动态重分布。GBase 8a数据库集群通过_t_gcluster_distinct_multi_redist和_t_gcluster_hash_redistribute_groupby_on_multiple_expression参数,可以指定多个列参与hash计算,避免GBase 8a做distinct、group、join时通过指定多列动态Hash重分布避免严重数据倾斜导致的性能问题。

常见业务场景如下

select distinct id1,id2,id3... from table,
select id1,id2,count(*) from table group by id1,id2
select a.* from a left join b on a.id=b.id and a.id2=b.id2

当然,如果有hash列参与,或者第一个列没有比较严重的倾斜,那么这个参数就不要用了。注意倾斜的定义,可以参考执行时间,如果每次都是其它节点很快结束,某一个或几个节点耗时长,比如超过30%。 查看gnode计算节点上sql的方法,请参考 GBase 8a数据库集群运维巡检维护常用命令简化版

提醒

本文介绍的2个参数,都是下划线开头的,代表参数并没有正式对外开放使用,也不保证新版本还会保留,就算保留也不确保是相同的名字。所以如果在某个版本可用,后续升级时请咨询厂家获得变动列表并进行测试。

distinct 操作

参数

默认值为1,可选值为1和2。

_t_gcluster_distinct_multi_redist

参数为1,按照单列计算进行重分布去重
参数为2,按照多列计算进行重分布去重

说明

常量、标量子查询不会作为重分布列。

设置该参数为2,由于在hash阶段计算了多个列,随着distinct列的增多,计算成本增加,时间增长,所以要以测试结果为准,在数据倾斜和性能之间平衡。

另外,distinct,group等有多个列的场景,无论什么情况,都建议将唯一值多的放第一个。

样例

表结构

如下是一个2个列的随机分布表,没有hash分布列。

gbase> desc t1;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id    | int(11)      | YES  |     | NULL    |       |
| name  | varchar(100) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (Elapsed: 00:00:00.00)

gbase> show create table t1;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                          |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| t1    | CREATE TABLE "t1" (
  "id" int(11) DEFAULT NULL,
  "name" varchar(100) DEFAULT NULL
) ENGINE=EXPRESS DEFAULT CHARSET=utf8 TABLESPACE='sys_tablespace' |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (Elapsed: 00:00:00.00)

默认的执行计划

可以看到REDIST重分布时,用ID列。

gbase> explain select distinct id,name from t1;
+----+--------------+-----------+---------+-----------+
| ID | MOTION       | OPERATION | TABLE   | CONDITION |
+----+--------------+-----------+---------+-----------+
| 01 | [RESULT]     |  Step     | <00>    |           |
|    |              |  AGG      |         |           |
| 00 | [REDIST(id)] |  Table    | t1[DIS] |           |
|    |              |  AGG      |         |           |
+----+--------------+-----------+---------+-----------+
4 rows in set (Elapsed: 00:00:00.00)

参数后的执行计划

修改参数等于2后,重分布REDIST采用了id,name两个列。

gbase> set _t_gcluster_distinct_multi_redist=2;
Query OK, 0 rows affected (Elapsed: 00:00:00.00)

gbase> explain select distinct id,name from t1;
+----+-------------------+-----------+---------+-----------+
| ID | MOTION            | OPERATION | TABLE   | CONDITION |
+----+-------------------+-----------+---------+-----------+
| 01 | [RESULT]          |  Step     | <00>    |           |
|    |                   |  AGG      |         |           |
| 00 | [REDIST(id,name)] |  Table    | t1[DIS] |           |
|    |                   |  AGG      |         |           |
+----+-------------------+-----------+---------+-----------+
4 rows in set (Elapsed: 00:00:00.00)

group操作

参数

默认值是0,使用1个列做重分布。1是用多个列做重分布。

_t_gcluster_hash_redistribute_groupby_on_multiple_expression

说明

常量、标量子查询不会作为重分布列。

设置该参数为1,由于在hash阶段计算了多个列,随着group列的增多,计算成本增加,时间增长,所以要以测试结果为准,在数据倾斜和性能之间平衡。

另外,distinct,group等有多个列的场景,无论什么情况,都建议将唯一值多的放第一个。

样例

表结构

如下是一个2个列的随机分布表,没有hash分布列。

gbase> desc t1;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id    | int(11)      | YES  |     | NULL    |       |
| name  | varchar(100) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (Elapsed: 00:00:00.00)

gbase> show create table t1;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                          |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| t1    | CREATE TABLE "t1" (
  "id" int(11) DEFAULT NULL,
  "name" varchar(100) DEFAULT NULL
) ENGINE=EXPRESS DEFAULT CHARSET=utf8 TABLESPACE='sys_tablespace' |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (Elapsed: 00:00:00.00)

默认的执行计划

可以看到REDIST重分布时,用ID列。

gbase> explain select id,name,count(*) from t1 group by id,name;
+----+--------------+-----------+---------+-------------------+
| ID | MOTION       | OPERATION | TABLE   | CONDITION         |
+----+--------------+-----------+---------+-------------------+
| 01 | [RESULT]     |  Step     | <00>    |                   |
|    |              |  GROUP    |         | GROUP BY id, name |
| 00 | [REDIST(id)] |  Table    | t1[DIS] |                   |
|    |              |  GROUP    |         | GROUP BY id, name |
+----+--------------+-----------+---------+-------------------+
4 rows in set (Elapsed: 00:00:00.01)

参数后的执行计划

修改参数等于1后,重分布REDIST采用了id,name两个列。

gbase> set _t_gcluster_hash_redistribute_groupby_on_multiple_expression=1;
Query OK, 0 rows affected (Elapsed: 00:00:00.00)

gbase> explain select id,name,count(*) from t1 group by id,name;
+----+-------------------+-----------+---------+-------------------+
| ID | MOTION            | OPERATION | TABLE   | CONDITION         |
+----+-------------------+-----------+---------+-------------------+
| 01 | [RESULT]          |  Step     | <00>    |                   |
|    |                   |  GROUP    |         | GROUP BY id, name |
| 00 | [REDIST(id,name)] |  Table    | t1[DIS] |                   |
|    |                   |  GROUP    |         | GROUP BY id, name |
+----+-------------------+-----------+---------+-------------------+
4 rows in set (Elapsed: 00:00:00.00)

join 操作

参数

默认值为1,可选值为1和2。

_t_gcluster_join_multi_hash_optimize_level=

参数为1,按照单列计算进行hash重分布
参数为2,按照多列计算进行重分布

说明

常量、标量子查询不会作为重分布列。

设置该参数为2,由于在hash阶段计算了多个列,随着hash条件列的增多,计算成本增加,时间增长,所以要以测试结果为准,在数据倾斜和性能之间平衡。

样例

表结构

如下是2个随机分布表,没有hash分布列。

gbase> desc t1;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id    | int(11)      | YES  |     | NULL    |       |
| name  | varchar(100) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (Elapsed: 00:00:00.00)

gbase> show create table t1;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                          |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| t1    | CREATE TABLE "t1" (
  "id" int(11) DEFAULT NULL,
  "name" varchar(100) DEFAULT NULL
) ENGINE=EXPRESS DEFAULT CHARSET=utf8 TABLESPACE='sys_tablespace' |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (Elapsed: 00:00:00.00)

gbase> show create table t3;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                          |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| t3    | CREATE TABLE "t3" (
  "id" int(11) DEFAULT NULL,
  "name" varchar(100) DEFAULT NULL
) ENGINE=EXPRESS DEFAULT CHARSET=utf8 TABLESPACE='sys_tablespace' |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (Elapsed: 00:00:00.00)

默认的执行计划

可以看到REDIST重分布时,用了name列。

gbase> explain select t1.id,t1.name from t1 left join t3 on t1.id=t3.id and t1.name=t3.name;
+----+----------------+------------+---------+-----------------------------+
| ID | MOTION         | OPERATION  | TABLE   | CONDITION                   |
+----+----------------+------------+---------+-----------------------------+
| 02 | [RESULT]       |  LEFT JOIN |         | (id = id) AND (name = name) |
|    |                |   Step     | <00>    |                             |
|    |                |   Step     | <01>    |                             |
| 01 | [REDIST(name)] |  Table     | t3[DIS] |                             |
| 00 | [REDIST(name)] |  Table     | t1[DIS] |                             |
+----+----------------+------------+---------+-----------------------------+
5 rows in set (Elapsed: 00:00:00.00)

参数后的执行计划

修改参数等于2后,重分布REDIST采用了id,name两个列。

gbase> set _t_gcluster_join_multi_hash_optimize_level=2;
Query OK, 0 rows affected (Elapsed: 00:00:00.00)

gbase> explain select t1.id,t1.name from t1 left join t3 on t1.id=t3.id and t1.name=t3.name;
+----+-------------------+------------+---------+-----------------------------+
| ID | MOTION            | OPERATION  | TABLE   | CONDITION                   |
+----+-------------------+------------+---------+-----------------------------+
| 02 | [RESULT]          |  LEFT JOIN |         | (id = id) AND (name = name) |
|    |                   |   Step     | <00>    |                             |
|    |                   |   Step     | <01>    |                             |
| 01 | [REDIST(id,name)] |  Table     | t3[DIS] |                             |
| 00 | [REDIST(id,name)] |  Table     | t1[DIS] |                             |
+----+-------------------+------------+---------+-----------------------------+
5 rows in set (Elapsed: 00:00:00.01)

注意

有时一个表会拉成复制表,不会参与重分布。

gbase> explain select t1.id,t1.name from t1 left join t3 on t1.id=t3.id and t1.name=t3.name;
+----+-------------+------------+---------+-----------------------------+
| ID | MOTION      | OPERATION  | TABLE   | CONDITION                   |
+----+-------------+------------+---------+-----------------------------+
| 01 | [RESULT]    |  LEFT JOIN |         | (id = id) AND (name = name) |
|    |             |   Table    | t1[DIS] |                             |
|    |             |   Step     | <00>    |                             |
| 00 | [BROADCAST] |  Table     | t3[DIS] |                             |
+----+-------------+------------+---------+-----------------------------+
4 rows in set (Elapsed: 00:00:00.00)

需要通过如下参数进行调优,但也许拉复制表性能更好呢?还是以测试结果为准。

gcluster_hash_redistribute_join_optimize=1

该参数详情参考 GBase 8a性能优化案例,大量小表join时拉了复制表导致只有1个节点运行,gcluster_hash_redistribute_join_optimize 参数调整


参考

GBase 8a 执行计划Explain介绍