1.1 SQL优化最佳实践
1.1.1 查询带分布键条件
通过explain查看执行计划,查看SQL语句是否使用到分布键,DN下发DN情况可通过Node/s: 关键字来查看。
如果使用了分布键,那么SQL只会下发到分布键对应的某个DN节点。
如果没有使用分布键,那么SQL会下发到所有DN节点。
没有带分布键的SQL,因为下发到了所有DN节点,消耗了更多的连接资源、计算资源,应尽量避免使用。
某个表的SQL语句可能有不同的where条件,在SQL优化时,应尽量确保高频并发的SQL语句是带了分布键条件。
例如,teledb_1表的分布键f2,下面的SQL where条件为f1=1,没有带分布键条件,那么SQL将下发到所有DN节点执行。
teledb=# explain select * from teledb_1 where f1=1;
QUERY PLAN
--------------------------------------------------------------------------------
Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0)
Node/s: dn001, dn002
-> Gather (cost=1000.00..7827.20 rows=1 width=14)
Workers Planned: 2
-> Parallel Seq Scan on teledb_1 (cost=0.00..6827.10 rows=1 width=14)
Filter: (f1 = 1)
(6 rows)
下面的SQL,where条件为f2=1,带了分布键条件,SQL只下发到了f2=1所在的DN节点dn001。
teledb=# explain select * from teledb_1 where f2=1;
QUERY PLAN
--------------------------------------------------------------------------------
Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0)
Node/s: dn001
-> Gather (cost=1000.00..7827.20 rows=1 width=14)
Workers Planned: 2
-> Parallel Seq Scan on teledb_1 (cost=0.00..6827.10 rows=1 width=14)
Filter: (f2 = 1)
(6 rows)
1.1.2 关联查询使用分布键关联
多表关联时,通过explain查看执行计划,查看SQL语句是否使用分布键(前面已介绍),查看SQL关联是否使用到了分布键。
如果两表关联都使用到了分布键,那么两表关联可以在dn节点内完成,不需要在dn节点之间交互数据;如果其中一张表没有用分布键,那么两表关联不能在dn节点内完成,该表需要在dn节点之间交互数据;如果两张表都没有使用分布键,那么两张表都需要在dn节点之间交互数据。
两表关联时,应优先使用两表的分布键关联,尽量保证高频并发的SQL都使用了分布键关联;其次至少有一张表用到了分布键,也可以减少一次数据重分布;两张表都没有用到分布键的场景应尽量避免。同时,两表关联时如果只能有一个表带分布键,那么更大的表、或查询结果集更大的表优先使用到分布键,让小一点的表去重分布数据。
在执行计划中,如果DN之间有数据重分布,那么执行计划中会有这样的关键字Distribute results by S: f1,这里表示按f1进行数据重分布。
例如,teledb_1的分布键为f2,teledb_2的分布键为f1,下面的SQL,两表关联时teledb_1没有用到分布键,两表关联不能在dn节点内完成,需要dn节点之间发起数据交互(重分布),teledb_1在完成表扫描后发起了按f1字段重分布的动作,对应执行计划中的Distribute results by S: f1。
teledb=# explain select teledb_1.* from teledb_1,teledb_2 where teledb_1.f1=teledb_2.f1 ;
QUERY PLAN
------------------------------------------------------------------------------------------------
Remote Subquery Scan on all (dn001,dn002) (cost=29.80..186.32 rows=3872 width=40)
-> Hash Join (cost=29.80..186.32 rows=3872 width=40)
Hash Cond: (teledb_1.f1 = teledb_2.f1)
-> Remote Subquery Scan on all (dn001,dn002) (cost=100.00..158.40 rows=880 width=40)
Distribute results by S: f1
-> Seq Scan on teledb_1 (cost=0.00..18.80 rows=880 width=40)
-> Hash (cost=18.80..18.80 rows=880 width=4)
-> Seq Scan on teledb_2 (cost=0.00..18.80 rows=880 width=4)
(8 rows)
例如下面的SQL,teledb_1和teledb_2关联时都用到了分布键,那么两表关联可以在dn节点内完成,不需要在dn节点之间交互数据。
teledb=# explain select teledb_1.* from teledb_1,teledb_2 where teledb_1.f2=teledb_2.f1 ;
QUERY PLAN
---------------------------------------------------------------------------------
Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0)
Node/s: dn001, dn002
-> Hash Join (cost=18904.69..46257.08 rows=500564 width=14)
Hash Cond: (teledb_1.f2 = teledb_2.f1)
-> Seq Scan on teledb_1 (cost=0.00..9225.64 rows=500564 width=14)
-> Hash (cost=9225.64..9225.64 rows=500564 width=4)
-> Seq Scan on teledb_2 (cost=0.00..9225.64 rows=500564 width=4)
(7 rows)
1.1.3 使用索引提高查询效率
通过explain查看执行计划,查看SQL语句是否使用了索引,Seq Scan表示对表进行了全表扫描,而如Index Scan,Index Only Scan则表示使用了索引扫描。
通常情况下,使用索引可以加速查询速度,但索引也会增加数据更新的开销,在数据量较小时,优化器也可能会使用全表扫描代替索引扫描。
例如,下面的SQL语句,使用了Parallel Seq Scan并行全表扫描。
teledb=# explain select * from teledb_2 where f3='1';
QUERY PLAN
--------------------------------------------------------------------------------
Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0)
Node/s: dn001, dn002
-> Gather (cost=1000.00..7827.20 rows=1 width=14)
Workers Planned: 2
-> Parallel Seq Scan on teledb_2 (cost=0.00..6827.10 rows=1 width=14)
Filter: (f3 = '1'::text)
(6 rows)
在f2字段上创建索引后,下面的SQL语句,使用了Index Scan索引扫描。
teledb=# create index teledb_2_f2_idx on teledb_2(f2);
CREATE INDEX
postgres=# explain select * from teledb_2 where f2=1;
QUERY PLAN
-------------------------------------------------------------------------------------
Remote Fast Query Execution (cost=0.00..0.00 rows=0 width=0)
Node/s: dn001, dn002
-> Index Scan using teledb_2_f2_idx on teledb_2 (cost=0.42..4.44 rows=1 width=14)
Index Cond: (f2 = 1)
(4 rows)
当然,按SQL优化原则,上述SQL语句where条件都没有带分布键,导致SQL下发到了所有DN节点,建议尝试优化为带分布键查询。
1.1.4 使用并行提高查询效率
在大表查询时,为充分利用服务器资源,可以尝试开启并行,多进程并发查询,提升查询效率。在执行计划中,算子前增加关键字Partial,同时有关键字 Workers Planned: xx,表示SQL使用了并行。
例如下面的SQL,未开启并行。
teledb=# explain select count(1) from teledb_1;
QUERY PLAN
---------------------------------------------------------------------------------------
Finalize Aggregate (cost=118.81..118.83 rows=1 width=8)
-> Remote Subquery Scan on all (dn001,dn002) (cost=118.80..118.81 rows=1 width=0)
-> Partial Aggregate (cost=18.80..18.81 rows=1 width=8)
-> Seq Scan on teledb_1 (cost=0.00..18.80 rows=880 width=0)
(4 rows)
下面是开启并行查询后的执行计划。
teledb=# explain select count(1) from teledb_1;
QUERY PLAN
----------------------------------------------------------------------------------------------------
Parallel Finalize Aggregate (cost=14728.45..14728.46 rows=1 width=8)
-> Parallel Remote Subquery Scan on all (dn001,dn002) (cost=14728.33..14728.45 rows=1 width=0)
-> Gather (cost=14628.33..14628.44 rows=1 width=8)
Workers Planned: 2
-> Partial Aggregate (cost=13628.33..13628.34 rows=1 width=8)
-> Parallel Seq Scan on teledb_1 (cost=0.00..12586.67 rows=416667 width=0)
(6 rows)
建议不要全局开启并行,仅在需要开启并行的具体SQL上开启,可通过会话级设置并行,或hint方式指定并行。
会话级设置如:
set max_parallel_workers_per_gather=2;
执行SQL;
set max_parallel_workers_per_gather=0;
1.1.5 根据需要设置关联发生的节点
多表关联的逻辑默认是在DN节点执行,在发生重分布后,可能会因为重分布原因性能明显下降。
针对此问题,TeleDB支持通过参数prefer_olap设置,可以选择将关联逻辑上拉到CN节点执行,避免DN之间的重分布。参数prefer_olap默认为on,表示关联下推到DN执行,off则表示关联上拉到DN执行。
这里需要注意,设置关联上拉CN节点执行,需要提前做好评估,如果上拉数据量过大,大量并发SQL发起上拉数据到CN动作,会导致CN节点负载过高,甚至OOM。
通常可以在TP类业务中可以针对SQL会话级设置上拉,性能会有所提升,同时CN节点应配置较大的资源,避免OOM发生。
例如,下面的SQL,teledb_1关联没有用到分布键,发生了重分布。
teledb=# explain select teledb_1.* from teledb_1,teledb_2 where teledb_1.f1=teledb_2.f1 ;
QUERY PLAN
------------------------------------------------------------------------------------------------
Remote Subquery Scan on all (dn001,dn002) (cost=29.80..186.32 rows=3872 width=40)
-> Hash Join (cost=29.80..186.32 rows=3872 width=40)
Hash Cond: (teledb_1.f1 = teledb_2.f1)
-> Remote Subquery Scan on all (dn001,dn002) (cost=100.00..158.40 rows=880 width=40)
Distribute results by S: f1
-> Seq Scan on teledb_1 (cost=0.00..18.80 rows=880 width=40)
-> Hash (cost=18.80..18.80 rows=880 width=4)
-> Seq Scan on teledb_2 (cost=0.00..18.80 rows=880 width=4)
(8 rows)
设置 prefer_olap=off后,关联上拉到CN上执行,重分布消失。
执行计划如下:
teledb=# set prefer_olap to off;
SET
teledb=# explain select teledb_1.* from teledb_1,teledb_2 where teledb_1.f1=teledb_2.f1 ;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Hash Join (cost=29.80..186.32 rows=3872 width=40)
Hash Cond: (teledb_1.f1 = teledb_2.f1)
-> Remote Subquery Scan on all (dn001,dn002) (cost=100.00..158.40 rows=880 width=40)
-> Seq Scan on teledb_1 (cost=0.00..18.80 rows=880 width=40)
-> Hash (cost=126.72..126.72 rows=880 width=4)
-> Remote Subquery Scan on all (dn001,dn002) (cost=100.00..126.72 rows=880 width=4)
-> Seq Scan on teledb_2 (cost=0.00..18.80 rows=880 width=4)
(7 rows)