Query执行过程
在Hologres中,当客户端发起一个SQL后,执行过程如下(以其中一个节点为例):
- Frontend(FE)节点对SQL进行解析和认证,并分发至执行引擎(Query Engine)的不同执行模块。
- 执行引擎(Query Engine)会根据SQL的特征走不同的执行路径。
- 如果是点查/点写的场景,会跳过优化器(Query Optimizer,QO),直接分发至后端获取数据,减少数据传送链路,从而实现更优的性能。整个执行链路也叫Fixed Plan,点查(与HBase的KV查询)、点写场景会直接走Fixed Plan。
- 如果是OLAP查询和写入场景:首先会由优化器(Query Optimizer,QO)对SQL进行解析,生成执行计划,在执行计划中会预估出算子执行Cost、统计信息、空间裁剪等。QO会通过生成的执行计划,决定使用HQE、PQE、SQE或者Hive QE对算子进行真正的计算。
- 执行引擎决定正确的执行计划,然后会通过存储引擎(Storage Engine,SE)进行数据获取,最后对每个Shard上的数据进行合并,返回至客户端。
Fixed Plan是Hologres独有的执行引擎优化方式,传统的SQL执行要经过优化器、协调器、查询引擎、存储引擎等多个组件,而Fixed Plan选择了短路径(Short-Cut)优化执行SQL,绕过了优化器、协调器、部分查询引擎的开销。通过Fixed FrontEnd直接对接Fixed Query Engine,实现SQL执行效率的成倍提升,是支持高吞吐实时写入,高并发查询的关键优化方法。
可以被Fixed Plan选中的SQL需要符合一定的条件,以及一定的参数配置,下面将会介绍这些配置参数以及SQL条件。
相关GUC参数
GUC列表
以下为fixed plan需要用到的参数配置:
说明:以下Fixed Plan相关参数,已经在Holo Client中默认打开,session级别生效。
GUC名称 |
适用场景 |
默认值 |
hg_experimental_enable_fixed_dispatcher |
查看实例的fixed plan是否打开。 |
on |
hg_experimental_enable_fixed_dispatcher_for_multi_values |
支持insert on conflict多行记录的Fixed Plan写入。建议客户端session级别打开。 注意:不保证原子性,即一次性写入多条的时候,如果没报错就是全部正常写入了,如果报错了只会报一条的错误,有可能全部没写入也有可能部分写入了部分没写入,没有写入的部分会将错误反馈给上层应用端,由应用端进行重试。 |
off |
hg_experimental_enable_fixed_dispatcher_autofill_series |
支持含有serial列的Fixed Plan写入。 建议客户端session级别打开。 |
off |
hg_experimental_enable_fixed_dispatcher_for_update |
支持更新(update)场景的Fixed Plan更新。 建议客户端session级别打开。 |
off |
hg_experimental_enable_fixed_dispatcher_for_delete |
支持删除(delete)场景的Fixed Plan删除。建议客户端session级别打开。 |
off |
hg_experimental_enable_fixed_dispatcher_for_scan |
支持PrefixScan场景的Fixed Plan查询。 说明:PrefixScan是指多列主键,查询条件只给前面几列主键 |
off |
hg_experimental_enable_bhclient_cache_on_session |
是否用cached on session模式,默认是cached on fe模式。 cache on session 和cache on fe的区别:
|
off |
GUC使用
1)查看GUC是否开启
通过show命令查看GUC是否开启:
show <GUC_name>;--示例:查看实例级别是否开启fixed planshow hg_experimental_enable_fixed_dispatcher;
2)session级别开启GUC
通过set命令可以在session级别设置GUC参数。session级别的参数只在当前session生效,当连接断开之后,将会失效,建议加在SQL前一起执行。
语法示例如下。
set<GUC_name>=<values>;
GUC_name为GUC参数的名称,values为GUC参数的值。
使用示例如下。
--insert on conflict多行记录支持Fixed Plan写入set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;
3)数据库级别
可以通过alter database xx set xxx命令来设置DB级别的GUC参数执,执行完成后在整个DB级别生效,设置完成后当前连接需要重新断开连接才能生效。新建DB不会生效,需要重新手动设置。语法示例如下。
alter database <db_name>set<GUC_name>=<values>;
其中:db_name为数据库名称,GUC_name为GUC参数的名称,values为GUC参数的值。
使用示例如下:
--DB级别开启fixed planalter database <db_name>set hg_experimental_enable_fixed_dispatcher =on;
对数据类型的要求
- 表的每一列都不能是MONEY或MONEY ARRAY
- DML(INSERT/UPDATE/DELETE)列 和 SELECT(select 的 target列和where里的列都要满足)列支持的类型:
- BOOLEAN(别名BOOL)
- SMALLINT
- INTEGER(别名INT或INT4)
- BIGINT(别名INT8)
- FLOAT(别名FLOAT4)
- DOUBLE PRECISION(别名FLOAT8)
- CHAR(n)
- VARCHAR(n)
- BYTEA
- JSON和JSONB
- TEXT(别名VARCHAR)
- TIMESTAMP WITH TIME ZONE(别名TIMESTAMPTZ)
- DATE
- TIMESTAMP
- DECIMAL(别名NUMERIC)
- ROARINGBITMAP
- 数组类型
- boolean[]
- smallint[]
- int4[]
- int8[]
- float4[]
- float8[]
- char(n)[]
- varchar(n)[]
- text[]
INSERT场景
Insert表达式
fixed plan支持以下insert表达式:
---写入单行insertintotable(col1,col2,col3..)values(?,?,?..)on conflict xxx;---写入多行insertintotable(col1,col2,col3..)values(?,?,?..),(?,?,?..)on conflict xxx;
- 支持内表,不支持外表
- 支持分区子表,不支持写分区主表(V1.3版本开始支持写分区主表)
Insert on conflict单行
支持场景:
- 支持没有on conflict表达式
- 支持on conflict do nothing表达式
- 支持on conflict do update时,必须update所有insert的非PK(Primary Key,主键,以下简称PK)的列,pk是否update都可以,并且只能是col = excluded.col方式更新
- 使用示例:
begin;createtable test_insert_oneline( pk1 int, pk2 int, col1 int, col2 int, primary key(pk1, pk2));commit;--update所有非PK列,可以Fixed Planinsertinto test_insert_oneline values(1,2,3,4)on conflict(pk1,pk2) do updateset col1 = excluded.col1, col2 = excluded.col2;--update所有列(包含PK和非PK),可以Fixed Planinsertinto test_insert_oneline values(1,2,3,4)on conflict(pk1,pk2) do updateset col1 = excluded.col1, col2 = excluded.col2, pk1 = excluded.pk1, pk2 = excluded.pk2;--必须update所有非pk的要insert的列,这个例子不包含col2,因此不能Fixed Planinsertinto test_insert_oneline values(1,2,3,4)on conflict(pk1,pk2) do updateset col1 = excluded.col1;--必须是set col = excluded.col方式更新,因此不能Fixed Planinsertinto test_insert_oneline values(1,2,3,4)on conflict(pk1,pk2) do updateset col1 = excluded.col1, col2 =5;
Insert on conflict多行
1)insert on conflict多行时,表达式如下:
set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;insertintotable(col1,col2,col3..)values(?,?,?..),(?,?,?..)on conflict xxx;
- 需要配置GUC hg_experimental_enable_fixed_dispatcher_for_multi_values为on
- 不保证原子性,即一次性写入多条的时候,如果没报错就是全部正常写入了,如果报错了有可能全部没写入也有可能部分写入了部分没写入
2)写入多行另一种写法:
set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;insertintotableselectunnest(ARRAY[true,false,true]::bool[]),unnest(ARRAY[1,2,3]::int4[]),unnest(ARRAY[1.11,2.222,3]::float4[])on conflict xxx;
- 需要配置hg_experimental_enable_fixed_dispatcher_for_multi_values为on
- 写入的列不能是数组类型
- unnest里ARRAY必须显式cast为对应列类型的数组类型
- 使用示例:
begin;createtable test_insert_multiline( pk1 int8, col1 float4, primary key(pk1));commit;--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_multi_values =on;insertinto test_insert_multiline select unnest(ARRAY[1,2,3]::int8[]), unnest(ARRAY[1.11,2.222,3]::float4[])on conflict do nothing;--unnest里ARRAY没有显式cast,不支持Fixed Planinsertinto test_insert_multiline select unnest(ARRAY[1,2,3]), unnest(ARRAY[1.11,2.222,3])on conflict do nothing;--第一列是int8,所以应该cast为int8[],这里例子是int4[],因此不支持Fixed Planinsertinto test_insert_multiline select unnest(ARRAY[1,2,3]::int4[]), unnest(ARRAY[1.11,2.222,3]::float4[])on conflict do nothing;
局部更新场景
Hologres支持通过主键,对表的部分列做更新,Fixed Plan同样可以支持局部更新的,需要满足以下条件
- Insert的列需要与update的列一一对应,包括数量和顺序
- 只能是col = excluded.col方式更新
Default列
表中含有default列时,写入时能走fixed plan的条件:
- 插入单条时支持
- 插入多条时,需要 实例版本>=1.1.36版本,若低于请升级。同时需要设置GUC参数hg_experimental_enable_fixed_dispatcher_for_multi_values为on
- 有default列的表,不支持insert on conflict表达式的Fixed Plan
- 使用示例:
begin;createtable test_insert_default( pk1 int, col1 int default 99, primary key(pk1));commit;--支持Fixed Planinsertinto test_insert_default(pk1)values(1);--需要 V1.1.36+版本支持set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;insertinto test_insert_default(pk1)values(1),(2),(3);
Serial列
表带有自增序列serial时,支持单条或者多条写入时走fixed plan,使用条件如下:
- 需要配置GUC hg_experimental_enable_fixed_dispatcher_autofill_series为on
- 写多行时需要配置GUC参数hg_experimental_enable_fixed_dispatcher_for_multi_values为on
- 有serial列的表,不支持insert on conflict表达式的Fixed Plan
begin;createtable test_insert_serial( pk1 int, col1 serial, primary key(pk1));commit;--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_autofill_series =on;insertinto test_insert_serial (pk1)values(1);--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_autofill_series =on;set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;insertinto test_insert_serial (pk1)values(1),(2),(3);
UPDATE场景
Update表达式
update时能走fixed plan的表达式如下:
set hg_experimental_enable_fixed_dispatcher_for_update =on;updatetableset col1 = ?, col2 =? where pk1 =? and pk2 =?;
- 需要配置GUC hg_experimental_enable_fixed_dispatcher_for_update 为on。
- 支持内表,不支持外表
- 不能是分区主表,支持分区子表
- 表必须有主键(pk)
Update的使用条件
updtae场景支持的情况如下:
- set 的列不能是pk
- where 里有且只有全部pk
- 可以用pk in (?,?,?) 或 pk = ANY() 一次性改多条e.g. pk1 in (1,2) and pk2 = any('{3,4}') and pk3 = 5 <=> 改(1,3,5),(1,4,5),(2,3,5),(2,4,5) 四条
- where里同一列只能有一个条件(一模一样的视为一个条件)
- 使用示例:
begin;createtable test_update( pk1 int, pk2 int, col1 int, col2 int, primary key(pk1, pk2));commit;--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_update =on;update test_update set col1 =1, col2 =2where pk1 =3and pk2 =4;--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_update =on;update test_update set col1 =1where pk1 =3and pk2 =4;--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_update =on;update test_update set col1 =1, col2 =2where pk1 in(1,2)and pk2 = any('{3,4}');--pk1多个过滤条件,不支持Fixed Planupdate test_update set col1 =1, col2 =2where pk1 =3and pk1 =4;--pk1多个过滤条件,不支持Fixed Planupdate test_update set col1 =1, col2 =2where pk1 in(1,2)and pk1 =1;--pk1多个过滤条件,但过滤条件相同,支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_update =on;update test_update set col1 =1, col2 =2where pk1 in(1,2)and pk1 in(1,2)and pk2 =4;
DELETE场景
Delete表达式
set hg_experimental_enable_fixed_dispatcher_for_delete =on;deletefromtablewhere pk1 =? and pk2 =? and pk3 =?;
- 需要配置GUC hg_experimental_enable_fixed_dispatcher_for_delete为on
- 支持内表,不支持外表
- 不支持分区主表,支持分区子表
- 表必须有pk
Delete的使用条件
delete场景支持的情况如下:
- where 里有且只有全部pk
- 可以用pk in (?,?,?) 或 pk = ANY() 一次性删多条e.g. pk1 in (1,2) and pk2 = any('{3,4}') and pk3 = 5 <=> 删(1,3,5),(1,4,5),(2,3,5),(2,4,5) 四条
- 同一列只能有一个条件(一模一样的视为一个条件)
- 使用示例:
begin;createtable test_delete( pk1 int, pk2 int, col1 int, col2 int, primary key(pk1, pk2));commit;--支持Fixed Plan,更多场景与Update样例一致set hg_experimental_enable_fixed_dispatcher_for_delete =on;deletefrom test_delete where pk1 =1and pk2 =2;
SELECT场景
Select表达式
select col1,col2,col3,...fromtablewhere pk1 =? and pk2 =? and pk3 =?;
- 支持内表,不支持外表
- 不支持分区主表,支持分区子表
- 表必须有pk
点查(key/value)场景
点查场景支持的情况如下:如下:
- where 里有且只有全部pk
- 可以用pk in (?,?,?) 或 pk = ANY() 一次性查多条e.g. pk1 in (1,2) and pk2 = any('{3,4}') and pk3 = 5 <=> 查(1,3,5),(1,4,5),(2,3,5),(2,4,5) 四条
- 同一列只能有一个条件(一模一样的视为一个条件)
- 如果有limit, limit的值必须 >0
begin;createtable test_select( pk1 int, pk2 int, col1 int, col2 int, primary key(pk1, pk2));commit;--支持Fixed Plan,更多场景条件与Update样例一致select*from test_select where pk1 =1and pk2 =2;
PrefixScan场景
PrefixScan场景是指表有多个主键,查询时按照左匹配原则只查几列主键。查询如下:
set hg_experimental_enable_fixed_dispatcher_for_scan =on;select col1,col2,col3,...fromtablewhere pk1 =? and pk2 =?;select col1,col2,col3,...fromtablewhere pk1 =? and pk2 >? and pk 2<?;--从1.1.48版本开始支持pk最后一列条件为rangeselect col1,col2,col3,...fromtablewhere pk1 =? and pk2 between? and?;--从1.1.48版本开始支持pk最后一列条件为range
- 需要配置GUC hg_experimental_enable_fixed_dispatcher_for_scan为on,且V1.1.24+版本
- PrefixScan从1.1.48版本开始支持pk最后一列条件为range。
说明:prefixScan一次性返回所有结果行,如果结果的字节数大于hg_experimental_fixed_scan_bytesize_limit会报错 scan result size larger than fixed scan size limit,可以通过配置hg_experimental_fixed_scan_bytesize_limit设置更符合场景的值,默认1048576,即1MB。
- 表必须有distribution key
PrefixScan的使用条件如下:
- where里有且只有pk的prefix。prefix定义: 若pk为(pk1,pk2,pk3) (pk1),(pk1,pk2)为prefix
- where里必须包含所有的distribution key
- e.g. 若表pk为(pk1,pk2,pk3,pk4), distribution key 为 'pk1,pk3',支持情况如下:
begin;createtable test_select_prefix( pk1 int, pk2 int, pk3 int, pk4 int, primary key(pk1, pk2,pk3,pk4));call set_table_property('test_select_prefix','distribution_key','pk1,pk3');commit;--没有包含所有distribution key,不能走fixed planselect*from test_select_prefix where pk1 =? and pk2 =?;--不是pk的prefix,不能走fixed planselect*from test_select_prefix where pk1 =? and pk3 =?;--可以走fixed planset hg_experimental_enable_fixed_dispatcher_for_scan =on;select*from test_select_prefix where pk1 =? and pk2 =? and pk3 =?;
- 可以用pk in (?,?,?) 或 pk = ANY() 一次性查多条
pk1 in (1,2) and pk2 = 3 <=> scan(1,3),(2,3)两组
pk2 =any('{3,4}') and pk1 in (1,2) <=> scan(1,3),(1,4),(2,3),(2,4)四组
- 同一列只能有一个条件(一模一样的视为一个条件)
- 如果有limit,limit的值必须 >0
- 使用示例如下:
begin;createtable test_scan( pk1 int, pk2 int, pk3 int, col1 int, primary key(pk1, pk2, pk3));CALL SET_TABLE_PROPERTY ('test_scan','distribution_key','pk1,pk2');commit;INSERTINTO test_scan values(1,2,3,4);--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_scan =on;select*from test_scan where pk1 =1and pk2 =2;--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_scan =on;select*from test_scan where pk1 =1and pk2 in(2,3);--支持Fixed Planset hg_experimental_enable_fixed_dispatcher_for_scan =on;select*from test_scan where pk1 = ANY('{3,4}')and pk2 in(2,3);--支持fixed plan,pk最后一列是range条件,需要1.1.48及以上版本支持set hg_experimental_enable_fixed_dispatcher_for_scan =on;select*from test_scan where pk1 =1and pk2 >1and pk2 <3;--支持fixed plan,pk最后一列是range条件,需要1.1.48及以上版本支持set hg_experimental_enable_fixed_dispatcher_for_scan =on;select*from test_scan where pk1 =1and pk2 between1and3;--不包含所有的distribution key,不支持Fixed Planselect*from test_scan where pk1 =1;--不符合主键前缀Prefix,不支持Fixed Planselect*from test_scan where pk2 =2;
验证Fixed Plan
通过FixedPlan执行的更新类SQL,在控制台的实时导入RPS面板中会显示为SDK类型,包括Insert、Update和Delete类型的操作。建议实时写入类Insert、Update、Delete都尽量优化为Fixed Plan方案,改善数据更新的效率。
也可以通过查看SQL执行计划(explain sql)验证是否符合Fixed Plan,如果返回的执行计划中有FixedXXXNode,既表示触发了Fixed Plan。如未生成FixedXXXNode 执行计划,请对照 上文支持场景 对照是否满足条件。
性能调优
在某些场景上若是已经开启Fixed Plan但还需要做性能调优时,可选择如下方式。
- Hologres V1.1.49版本开始针对Fixed Plan点查场景进行了优化,在大规模点查的情况下提升了30%以上吞吐。若有需要请升级实例至V1.1.49及以上版本。
- 客户端合理的攒批(使用Holo Client会自动攒批),即一次执行SQL命令的数量,实践证明数量为512或者512的倍数性能会更好。
了解Hologres:https://www.aliyun.com/product/bigdata/hologram