Fixed Plan是Hologres独有的执行引擎优化方式,本文将为您介绍可以被Fixed Plan选中的SQL需要符合的条件和参数配置。

背景信息

Fixed Plan是Hologres独有的执行引擎优化方式,传统的SQL执行要经过优化器、协调器、查询引擎、存储引擎等多个组件,而Fixed Plan选择了短路径(Short-Cut)优化执行SQL,绕过了优化器、协调器、部分查询引擎的开销。通过Fixed FrontEnd直接对接Fixed Query Engine,实现SQL执行效率的成倍提升,是支持高吞吐实时写入,高并发查询的关键优化方法。关于Fixed Plan的介绍请参见产品架构

相关GUC参数

  • GUC列表

    以下为Fixed Plan需要用到的参数配置,所有参数取值为on或者off。所有参数已经在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 支持更改缓存模式,存在以下两种情况。
    • on:使用cached on session模式。
    • off:使用cached on fe模式。
    说明 cached on sessioncached on fe的区别如下。
    • cached on session是每个连接拥有自己的Writer、Reader,单连接的吞吐更好,但启动会更慢(每个表第一次进行读或写时需要有启动时间)。
    • cached on fe是FE(Frontend)节点上所有连接共享Writer、Reader,连接断开后Writer、Reader不会关闭,所以总体上没有启动时间。
    off
  • GUC使用
    • 查看GUC是否开启
      通过show命令查看GUC是否开启,命令语句如下。
      show <GUC_name>;
      使用示例如下。
      -- 查看实例级别是否开启Fixed Plan
      show hg_experimental_enable_fixed_dispatcher;
    • 开启GUC
      • 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;
      • 数据库级别开启GUC
        通过alter database xx set xxx命令来设置数据库级别的GUC参数,执行完成后在整个数据库生效,设置完成后当前连接需要重新断开连接才能生效。新建数据库不会生效,需要重新手动设置,语法示例如下。
        alter database <db_name> set <GUC_name> = <values>;
        db_name为数据库名称,GUC_name为GUC参数的名称,values为GUC参数的值。
        使用示例如下。
        --DB级别开启fixed plan
        alter 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表达式。
    -- 写入单行
    insert into table(col1,col2,col3..) values(?,?,?..) on conflict xxx;
    -- 写入多行
    insert into table(col1,col2,col3..) values(?,?,?..),(?,?,?..) on conflict xxx;
    说明
    • 支持使用INSERT命令写入内部表,不支持外部表。
    • 支持使用INSERT命令写入分区表,Hologres V1.3及以上版本支持写入分区父表。
  • Insert on conflict单行
    • 支持场景如下。
      • 支持没有on conflict的表达式。
      • 支持含有on conflict do nothing的表达式。
      • 支持on conflict do update,必须更新所有insert的非PK(Primary Key,主键,以下简称PK)列,PK是否更新都可以,并且只能是col = excluded.col方式更新。Hologres V1.3及以上版本支持更新部分非PK列,但是仍仅支持col = excluded.col方式更新
    • 使用示例如下。
      begin;
      create table test_insert_oneline(
        pk1 int,
        pk2 int,
        col1 int,
        col2 int,
        primary key(pk1, pk2)
      );
      commit;
      
      --update所有非PK列,可以Fixed Plan
      insert into test_insert_oneline values(1,2,3,4) on conflict(pk1,pk2) do update set col1 = excluded.col1, col2 = excluded.col2;
      
      --update所有列(包含PK和非PK),可以Fixed Plan
      insert into test_insert_oneline values(1,2,3,4) on conflict(pk1,pk2) do update set col1 = excluded.col1, col2 = excluded.col2, pk1 = excluded.pk1, pk2 = excluded.pk2;
      
      --必须update所有非pk的要insert的列,此例子不包含col2,仅Hologres V1.3仅以上版本支持Fixed Plan
      insert into test_insert_oneline values(1,2,3,4) on conflict(pk1,pk2) do update set col1 = excluded.col1;
      
      --必须是set col = excluded.col方式更新,因此不能Fixed Plan
      insert into test_insert_oneline values(1,2,3,4) on conflict(pk1,pk2) do update set col1 = excluded.col1, col2 = 5;
  • Insert on conflict多行
    • insert on conflict多行时,表达式如下。
      set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;
      insert into table(col1,col2,col3..) values(?,?,?..),(?,?,?..) on conflict xxx;
      • 需要配置GUC参数:hg_experimental_enable_fixed_dispatcher_for_multi_values=on;
      • 不保证原子性,即一次性写入多条的时候,如果没报错就是全部正常写入了,如果报错了有可能全部没写入也有可能部分写入了部分没写入。
    • 写入多行另一种写法表达式如下。
      set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;
      insert into table select
      unnest(ARRAY[true, false, true]::bool[]),
      unnest(ARRAY[1,2,3]::int4[]),
      unnest(ARRAY[1.11,2.222,3]::float4[]) on conflict xxx;
      • 需要配置GUC参数:hg_experimental_enable_fixed_dispatcher_for_multi_values=on;
      • 写入的列不能是数组类型。
      • unnest里ARRAY必须显式转换为对应列类型的数组类型。
      使用示例如下。
      begin;
      create table test_insert_multiline(
        pk1 int8,
        col1 float4,
        primary key(pk1)
      );
      commit;
      
      --支持Fixed Plan
      set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;
      insert into 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 Plan
      insert into 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 Plan
      insert into 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的条件如下。
    • 插入单条数据时支持。
    • 插入多条数据时,需要Hologres实例在V1.1.36及以上版本,若低于此版本请升级实例。
    • 配置GUC参数:hg_experimental_enable_fixed_dispatcher_for_multi_values=on;
    • Hologres V1.3及以上版本支持有Default列的表insert on conflict表达式的Fixed Plan。之前版本实例有Default列的表,不支持insert on conflict表达式的Fixed Plan。
    使用示例如下。
    begin;
    create table test_insert_default(
      pk1 int,
      col1 int default 99,
      primary key(pk1)
    );
    commit;
    
    --支持Fixed Plan
    insert into test_insert_default(pk1) values(1);
    
    --需要V1.1.36及以上版本支持
    set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;
    insert into test_insert_default(pk1) values(1),(2),(3);
  • Serial列
    表带有自增序列Serial时,支持单条或者多条写入进行Fixed Plan的条件如下。
    • 配置GUC参数:set hg_experimental_enable_fixed_dispatcher_autofill_series=on;
    • 插入多条数据时,还需配置GUC参数:set hg_experimental_enable_fixed_dispatcher_for_multi_values=on;
    • 有Serial列的表,不支持insert on conflict表达式的Fixed Plan。
    使用示例如下。
    begin;
    create table test_insert_serial(
      pk1 int,
      col1 serial,
      primary key(pk1)
    );
    commit;
    
    --支持Fixed Plan
    set hg_experimental_enable_fixed_dispatcher_autofill_series =on;
    insert into test_insert_serial (pk1) values(1);
    
    --支持Fixed Plan
    set hg_experimental_enable_fixed_dispatcher_autofill_series =on;
    set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;
    insert into test_insert_serial (pk1) values(1),(2),(3);

UPDATE场景

  • Update表达式
    Update时能进行Fixed Plan的表达式如下。
    set hg_experimental_enable_fixed_dispatcher_for_update =on;
    update table set col1 = ?, col2 = ? where pk1 = ? and pk2 = ?;
    • 需要配置GUC参数:hg_experimental_enable_fixed_dispatcher_for_update=on;
    • 支持Update内部表,不支持外部表。
    • 支持分区子表,不支持分区父表。
    • 表必须有主键(PK)。
  • Update场景使用
    Update场景支持进行Fixed Plan的条件如下。
    • set的列不能是主键(PK)。
    • where条件里有且只能有全部的PK。
    • 可以使用pk in (?,?,?) 或 pk = ANY() 一次修改多条。示例: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;
    create table test_update(
      pk1 int,
      pk2 int,
      col1 int,
      col2 int,
      primary key(pk1, pk2)
    );
    commit;
    
    --支持Fixed Plan
    set hg_experimental_enable_fixed_dispatcher_for_update =on;
    
    update test_update set col1 = 1, col2 = 2 where pk1 = 3 and pk2 = 4;
    
    --支持Fixed Plan
    set hg_experimental_enable_fixed_dispatcher_for_update =on;
    update test_update set col1 = 1 where pk1 = 3 and pk2 = 4;
    
    --支持Fixed Plan
    set hg_experimental_enable_fixed_dispatcher_for_update =on;
    update test_update set col1 = 1, col2 = 2 where pk1 in (1,2) and pk2 = any('{3,4}');
    
    --pk1多个过滤条件,不支持Fixed Plan
    update test_update set col1 = 1, col2 = 2 where pk1 = 3 and pk1 = 4;
    
    --pk1多个过滤条件,不支持Fixed Plan
    update test_update set col1 = 1, col2 = 2 where pk1 in (1,2) and pk1 = 1;
    
    --pk1多个过滤条件,但过滤条件相同,支持Fixed Plan
    set hg_experimental_enable_fixed_dispatcher_for_update =on;
    update test_update set col1 = 1, col2 = 2 where pk1 in (1,2) and pk1 in (1,2) and pk2 =4;

DELETE场景

  • Delete表达式
    Delete时能进行Fixed Plan的表达式如下。
    set hg_experimental_enable_fixed_dispatcher_for_delete =on;
    delete from table where pk1 = ? and pk2 = ? and pk3 = ?;
    • 需要配置GUC参数:hg_experimental_enable_fixed_dispatcher_for_delete=on;
    • 支持Delete内部表,不支持外部表。
    • 支持分区子表,不支持分区父表。
    • 表必须有主键(PK)。
  • Delete场景使用
    Delete场景支持进行Fixed Plan的条件如下。
    • where条件里有且只能有全部的PK。
    • 可以使用pk in (?,?,?) 或 pk = ANY() 一次删除多条。示例: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;
    create table 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;
    delete from test_delete where pk1 = 1 and pk2 = 2;

SELECT场景

  • SELECT表达式
    SELECT时能进行Fixed Plan的表达式如下。
    select col1,col2,col3,... from table where pk1 = ? and pk2 = ? and pk3 = ?;
    • 支持Select内部表,不支持外部表。
    • 支持分区子表,不支持分区父表。
    • 表必须有主键(PK)。
  • 点查(key/value)场景
    点查场景支持的情况如下。
    • where条件里有且只能有全部的PK。
    • 可以使用pk in (?,?,?) 或 pk = ANY() 一次查多条。示例: 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;
    create table test_select(
      pk1 int,
      pk2 int,
      col1 int,
      col2 int,
      primary key(pk1, pk2)
    );
    commit;
    
    --支持Fixed Plan
    select * from test_select where pk1 = 1 and pk2 = 2;
  • PrefixScan场景
    • PrefixScan场景表达式
      PrefixScan场景是指表有多个主键,查询时按照左匹配原则只查几列主键,查询表达式如下。
      set hg_experimental_enable_fixed_dispatcher_for_scan = on;
      select col1,col2,col3,... from table where pk1 = ? and pk2 = ?;
      select col1,col2,col3,... from table where pk1 = ? and pk2 > ? and pk2 < ?;--从1.1.48版本开始支持pk最后一列条件为range
      select col1,col2,col3,... from table where pk1 = ? and pk2 between ? and ?;--从1.1.48版本开始支持pk最后一列条件为range
                                      
      • 需要配置GUC参数:hg_experimental_enable_fixed_dispatcher_for_scan=on;,且实例在V1.1.24及以上版本 。
        说明 PrefixScan一次性返回所有结果行,如果结果的字节数大于hg_experimental_fixed_scan_bytesize_limit会报错:scan result size larger than fixed scan size limit ,可以通过配置hg_experimental_fixed_scan_bytesize_limit参数设置更符合场景的值,默认值为1048576,即1MB。
      • PrefixScan从V1.1.48版本开始支持主键最后一列条件设置为范围。
      • 表必须有Distribution Key。
    • PrefixScan使用
      PrefixScan的使用条件如下。
      • where里有且只有PK的Prefix。
        说明 Prefix定义: 若PK为(pk1,pk2,pk3),则(pk1),(pk1,pk2)为Prefix。
      • where里必须包含所有的Distribution Key。
      使用示例如下,若表PK为(pk1,pk2,pk3,pk4), 则Distribution Key为pk1,pk3
      begin;
      create table 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 plan
      select * from test_select_prefix where pk1 = ? and pk2 = ?;
      
      --不是pk的prefix,不能走fixed plan
      select * from test_select_prefix where pk1 = ? and pk3 =?;
      
      --可以走fixed plan
       set 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;
      create table 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;
      INSERT INTO test_scan values (1,2,3,4);
      
      --支持Fixed Plan
      set hg_experimental_enable_fixed_dispatcher_for_scan = on;
      select * from test_scan where pk1 = 1 and pk2 = 2;
      
      --支持Fixed Plan
      set hg_experimental_enable_fixed_dispatcher_for_scan = on;
      select * from test_scan where pk1 = 1 and pk2 in (2,3);
      
      --支持Fixed Plan
      set 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 = 1 and pk2 = 1 and pk3 > 1 and pk3 < 4;
      
      --支持fixed plan,pk最后一列是range条件,需要1.1.48及以上版本支持
      set hg_experimental_enable_fixed_dispatcher_for_scan = on;
      select * from test_scan where pk1 = 1 and pk2 = 1 and pk3 between 1 and 4;
      
      --不包含所有的distribution key,不支持Fixed Plan
      select * from test_scan where pk1 = 1;
      
      --不符合主键前缀Prefix,不支持Fixed Plan
      select * from test_scan where pk2 = 2;

验证Fixed Plan

  • 通过FixedPlan执行的更新类SQL,在控制台的实时导入RPS面板中会显示为SDK类型,包括INSERT、UPDATE和DELETE类型的操作。建议实时写入类Insert、Update、Delete都尽量优化为Fixed Plan方案,改善数据更新的效率。
  • 通过查看SQL执行计划(explain sql),如果返回的执行计划中含有FixedXXXNode,既表示触发了Fixed Plan,如下图所示。如未生成含有FixedXXXNode的执行计划,请对照上文场景支持条件, 查看是否满足条件。验证fixedplan

性能调优

在某些场景上若是已经开启Fixed Plan但还需要做性能调优时,可选择如下方式。
  • Hologres V1.1.49版本开始针对Fixed Plan点查场景进行了优化,在大规模点查的情况下提升了30%以上吞吐。若有需要请升级实例至V1.1.49及以上版本。
  • 客户端合理的攒批(使用Holo Client会自动攒批),即一次执行SQL命令的数量,实践证明数量为512或者512的倍数性能会更好。