Greenplum 优化CASE - 对齐JOIN字段类型,使用数组代替字符串,降低字符串处理开销,列存降低扫描开销

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 标签PostgreSQL , 数组 , 字符串 , 字符串处理 , JOIN , where , 类型一致性背景Greenplum通常被用作OLAP,在一些用户使用过程中,可能因为数据结构设计,SQL问题等原因导致性能不佳,虽然通过增加节点可以解决问题,但是如果能优化的话,可以节约不少硬件资源。

标签

PostgreSQL , 数组 , 字符串 , 字符串处理 , JOIN , where , 类型一致性


背景

Greenplum通常被用作OLAP,在一些用户使用过程中,可能因为数据结构设计,SQL问题等原因导致性能不佳,虽然通过增加节点可以解决问题,但是如果能优化的话,可以节约不少硬件资源。

例如

1、对齐JOIN字段类型。如果等值JOIN的字段类型不一致,无法使用HASH JOIN。

2、对齐where条件字段类型。同上,无法使用HASH JOIN,或者索引扫描。

3、使用数组代替字符串,降低字符串处理开销。如果字符串本身需要大量的格式化处理FILTER,那么使用数组的性能会好很多。

4、列存降低扫描开销,统计型的SQL由于涉及的字段有限,使用列存比行存储性能好很多。

例子

1、这个查询耗费230秒。

SELECT col4,count(DISTINCT c.col1) ptnum  
     from tbl1 a  
     INNER JOIN tbl2 b on b.col2=a.id  
     inner join tbl3 t2 on t2.ID <= (length(b.col3) - length(replace(b.col3,',',''))+1)   
     INNER JOIN tbl4 c   
     on replace(replace(Split_part(reverse(Split_part(reverse(Split_part(b.col3,',',cast(t2.id as int))),',',1)),':',1),'{',''),'"','') = c.id  
     INNER JOIN tbl5 s on a.col4=s.id  
     where replace(replace(reverse(Split_part(Split_part(reverse(Split_part(b.col3,',',cast(t2.id as int))),',',1),':',1)),'"',''),'}','') >'0'   
     and c.col1 not in ('xxxxxx')  
     GROUP BY col4;  

2、使用explain analyze分析瓶颈

3、问题:

3.1、JOIN类型不一致,导致未使用HASH JOIN。

3.2、有两个表JOIN时产生笛卡尔积来进行不等于的判断,数据量叠加后需要计算几十万亿次。

tbl2.col3字符串格式如下(需要计算几十万亿次)

{"2":"1","10":"1","13":"1","16":"1","21":"1","26":"1","28":"1","30":"1","32":"1","33":"1","34":"1","35":"1","36":"1","37":"1","39":"1","40":"1","99":"2","100":"2","113":"1","61":"1","63":"4","65":"2"}  

3.3、使用了行存储,查询时扫描的量较大,并且无法使用向量计算。

优化

1、使用列存代替行存(除nestloop的内表tbl3,继续使用索引FILTER)

create table tmp_tbl1 (like tbl1) WITH (APPENDONLY=true, ORIENTATION=column);  
insert into tmp_tbl1 select * from tbl1;  
create table tmp_tbl4 (like tbl4) WITH (APPENDONLY=true, ORIENTATION=column);  
insert into tmp_tbl4 select * from tbl4;  
create table tmp_tbl5 ( like tbl5) WITH (APPENDONLY=true, ORIENTATION=column);  
insert into tmp_tbl5 select * from tbl5;  
create table tmp_tbl2 (like tbl2) WITH (APPENDONLY=true, ORIENTATION=column) distributed by (col2);  
insert into tmp_tbl2 select * from tbl2;  

2、使用array代替text

alter table tmp_tbl2 alter column col3 type text[] using (case col3 when '[]' then '{}' else replace(col3,'"','') end)::text[];  

修改后的类型、内容如下

digoal=> select col3 from tmp_tbl2  limit 2;  
                                                    col3                                                       
------------------------------------------------------------------------------------------------------------------------  
 {63:1,65:1,70:1,71:1,73:1,75:1,77:1,45:3,78:1,54:2,44:1,80:1,36:1,84:1,96:2}  
 {2:2,10:1,13:1,16:1,30:1,107:1,26:1,28:1,32:1,33:1,34:1,35:1,36:1,37:1,39:1,99:2,100:2,113:1,40:1,57:1,63:2,64:1,65:4}  
(2 rows)  

3、join 字段保持一致

alter table tmp_tbl2 alter column col2 type int8;  

4、将原来的查询SQL修改成如下(字符串处理变成了数组)

(本例也可以使用二维数组,完全规避字符串处理。)

SELECT col4,count(DISTINCT c.col1) ptnum  
     from tmp_tbl1 a  
     INNER JOIN tmp_tbl2 b on b.col2=a.id  
     inner join tbl3 t2 on t2.ID <= array_length(col3,1)  -- 更改  
     INNER JOIN tmp_tbl4 c   
     on split_part(b.col3[cast(t2.id as int)], ':', 1) = c.id   
     INNER JOIN tmp_tbl5 s on a.col4=s.id  
     where split_part(b.col3[cast(t2.id as int)], ':', 2) > '0'   
     and c.col1 not in ('xxxxxx')  
     GROUP BY col4;   

执行计划

                                                                                           QUERY PLAN                                                                                              
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
 Gather Motion 32:1  (slice7; segments: 32)  (cost=543258065.87..543259314.50 rows=41621 width=12)  
   ->  GroupAggregate  (cost=543258065.87..543259314.50 rows=1301 width=12)  
         Group By: a.col4  
         ->  Sort  (cost=543258065.87..543258169.93 rows=1301 width=12)  
               Sort Key: a.col4  
               ->  Redistribute Motion 32:32  (slice6; segments: 32)  (cost=542355803.38..543254872.50 rows=1301 width=12)  
                     Hash Key: a.col4  
                     ->  GroupAggregate  (cost=542355803.38..543254040.08 rows=1301 width=12)  
                           Group By: a.col4  
                           ->  Sort  (cost=542355803.38..542655042.19 rows=3740486 width=11)  
                                 Sort Key: a.col4  
                                 ->  Redistribute Motion 32:32  (slice5; segments: 32)  (cost=6247.23..518770960.13 rows=3740486 width=11)  
                                       Hash Key: c.col1  
                                       ->  Hash Join  (cost=6247.23..516377049.63 rows=3740486 width=11)  
                                             Hash Cond: split_part(b.col3[t2.id::integer], ':'::text, 1) = c.id::text  
                                             ->  Nested Loop  (cost=5494.14..476568597.41 rows=3852199 width=491)  
                                                   Join Filter: split_part(b.col3[t2.id::integer], ':'::text, 2) > '0'::text  
                                                   ->  Broadcast Motion 32:32  (slice3; segments: 32)  (cost=5494.14..115247.73 rows=277289 width=483)  
                                                         ->  Hash Join  (cost=5494.14..23742.36 rows=8666 width=483)  
                                                               Hash Cond: b.col2 = a.id  
                                                               ->  Seq Scan on tmp_tbl2 b  (cost=0.00..14088.89 rows=8666 width=487)  
                                                               ->  Hash  (cost=4973.86..4973.86 rows=1301 width=12)  
                                                                     ->  Redistribute Motion 32:32  (slice2; segments: 32)  (cost=2280.93..4973.86 rows=1301 width=12)  
                                                                           Hash Key: a.id  
                                                                           ->  Hash Join  (cost=2280.93..4141.42 rows=1301 width=12)  
                                                                                 Hash Cond: s.id = a.col4  
                                                                                 ->  Append-only Columnar Scan on tmp_tbl5 s  (cost=0.00..1220.97 rows=1491 width=4)  
                                                                                 ->  Hash  (cost=1760.66..1760.66 rows=1301 width=12)  
                                                                                       ->  Redistribute Motion 32:32  (slice1; segments: 32)  (cost=0.00..1760.66 rows=1301 width=12)  
                                                                                             Hash Key: a.col4  
                                                                                             ->  Append-only Columnar Scan on tmp_tbl1 a  (cost=0.00..928.22 rows=1301 width=12)  
                                                   ->  Index Scan using idx_codeid on tbl3 t2  (cost=0.00..23.69 rows=42 width=8)  
                                                         Index Cond: t2.id <= array_length(b.col3, 1)  
                                             ->  Hash  (cost=364.69..364.69 rows=972 width=11)  
                                                   ->  Broadcast Motion 32:32  (slice4; segments: 32)  (cost=0.00..364.69 rows=972 width=11)  
                                                         ->  Append-only Columnar Scan on tmp_tbl4 c  (cost=0.00..44.26 rows=31 width=11)  
                                                               Filter: col1 <> 'xxxxxx'::text  
 Settings:  effective_cache_size=8GB; enable_nestloop=off; gp_statistics_use_fkeys=on  
 Optimizer status: legacy query optimizer  
(39 rows)  

性能提升

原来SQL响应时间: 230秒

修改后SQL响应时间: < 16秒

小结

瓶颈分析

1、JOIN时不等条件,必须使用笛卡尔的方式逐一判断,所以如果FILTER条件很耗时(CPU),那么性能肯定好不到哪去。

2、原来大量的reverse, split, replace字符串计算,很耗时。刚好落在笛卡尔上,计算数十万亿次。

3、JOIN字段类型不一致。未使用HASH JOIN。

4、分析SQL,未使用列存储。

优化手段

1、array 代替字符串。

2、改写SQL

3、对齐JOIN类型。

4、使用列存储。

5、保留的NESTLOOP JOIN,内表保持行存储,使用索引扫描。(如果是小表,可以使用物化扫描,更快)

6、analyze table;

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
SQL 索引 数据库
使用instr 函数优化替换Like条件子句提高数据检索性能案例总结
使用instr 函数优化替换Like条件子句提高数据检索性能
1148 0
使用instr 函数优化替换Like条件子句提高数据检索性能案例总结
|
3月前
|
SQL 存储 关系型数据库
ADBPG&Greenplum成本优化问题之排查存在前缀字段相同的多个复合索引如何解决
ADBPG&Greenplum成本优化问题之排查存在前缀字段相同的多个复合索引如何解决
37 2
|
3月前
|
SQL 存储 关系型数据库
ADBPG&Greenplum成本优化问题之查询大表的dead tuple占比和空间如何解决
ADBPG&Greenplum成本优化问题之查询大表的dead tuple占比和空间如何解决
34 1
|
6月前
|
缓存 关系型数据库 MySQL
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
294 0
|
6月前
|
存储 关系型数据库 MySQL
MySQL数据库性能大揭秘:表设计优化的高效策略(优化数据类型、增加冗余字段、拆分表以及使用非空约束)
MySQL数据库性能大揭秘:表设计优化的高效策略(优化数据类型、增加冗余字段、拆分表以及使用非空约束)
365 0
|
6月前
|
SQL 分布式计算 算法
当两个表进行Join操作时,如果它们的数据不符合MapJoin规范,您可以尝试以下优化方案
当两个表进行Join操作时,如果它们的数据不符合MapJoin规范,您可以尝试以下优化方案
86 4
|
SQL 存储 Oracle
前缀索引,在性能和空间中寻找平衡
前缀索引,在性能和空间中寻找平衡
|
存储 Oracle 关系型数据库
[MySQL优化案例]系列 — 优化InnoDB表BLOB列的存储效率
[MySQL优化案例]系列 — 优化InnoDB表BLOB列的存储效率
179 0
[MySQL优化案例]系列 — 优化InnoDB表BLOB列的存储效率
|
关系型数据库 PostgreSQL 移动开发
PostgreSQL 9.6 聚合运算180倍性能提升如何做到? 聚合代码优化OP复用浅析
PostgreSQL 9.6 内核优化之 聚合代码优化OP复用浅析 作者 digoal 日期 2016-10-08 标签 PostgreSQL , 9.6 , 内核优化 , 聚合代码优化 , OP复用 背景 聚合操作指将分组的数据聚合为一个结果输出。 聚合通常用在统
5435 0