文档中心MogDBMogDB StackUqbar
v5.0

文档:v5.0

支持的版本:

其他版本:

使用Plan Hint进行调优


Plan Hint调优概述

Plan Hint为用户提供了直接影响执行计划生成的手段,用户可以通过指定join顺序,join、scan方法,指定结果行数,等多个手段来进行执行计划的调优,以提升查询的性能。

MogDB还提供了SQL PATCH功能,在不修改业务语句的前提下通过创建SQL PATCH的方式使得Hint生效。

功能描述

Plan Hint支持在SELECT关键字后通过如下形式指定:

/*+ <plan hint>*/

可以同时指定多个hint,之间使用空格分隔。hint只能hint当前层的计划,对于子查询计划的hint,需要在子查询的select关键字后指定hint。

例如:

select /*+ <plan_hint1> <plan_hint2> */ * from t1, (select /*+ <plan_hint3> */ from t2) where 1=1;

其中<plan_hint1>,<plan_hint2>为外层查询的hint,<plan_hint3>为内层子查询的hint。

img 须知: 如果在视图定义(CREATE VIEW)时指定hint,则在该视图每次被应用时会使用该hint。 当使用random plan功能(参数plan_mode_seed不为0)时,查询指定的plan hint不会被使用。

支持范围

当前版本Plan Hint支持的范围如下,后续版本会进行增强。

  • 指定Join顺序的Hint - leading hint
  • 指定Join方式的Hint,仅支持除semi/anti join,unique plan之外的常用hint。
  • 指定结果集行数的Hint
  • 指定Scan方式的Hint,仅支持常用的tablescan,indexscan和indexonlyscan的hint。
  • 指定子链接块名的Hint

注意事项

不支持Agg、Sort、Setop和Subplan的hint。

示例

本章节使用同一个语句进行示例,便于Plan Hint支持的各方法作对比,示例语句及不带hint的原计划如下所示:

create table store
(
    s_store_sk                integer               not null,
    s_store_id                char(16)              not null,
    s_rec_start_date          date                          ,
    s_rec_end_date            date                          ,
    s_closed_date_sk          integer                       ,
    s_store_name              varchar(50)                   ,
    s_number_employees        integer                       ,
    s_floor_space             integer                       ,
    s_hours                   char(20)                      ,
    s_manager                 varchar(40)                   ,
    s_market_id               integer                       ,
    s_geography_class         varchar(100)                  ,
    s_market_desc             varchar(100)                  ,
    s_market_manager          varchar(40)                   ,
    s_division_id             integer                       ,
    s_division_name           varchar(50)                   ,
    s_company_id              integer                       ,
    s_company_name            varchar(50)                   ,
    s_street_number           varchar(10)                   ,
    s_street_name             varchar(60)                   ,
    s_street_type             char(15)                      ,
    s_suite_number            char(10)                      ,
    s_city                    varchar(60)                   ,
    s_county                  varchar(30)                   ,
    s_state                   char(2)                       ,
    s_zip                     char(10)                      ,
    s_country                 varchar(20)                   ,
    s_gmt_offset              decimal(5,2)                  ,
    s_tax_precentage          decimal(5,2)                  ,
    primary key (s_store_sk)
);
create table store_sales
(
    ss_sold_date_sk           integer                       ,
    ss_sold_time_sk           integer                       ,
    ss_item_sk                integer               not null,
    ss_customer_sk            integer                       ,
    ss_cdemo_sk               integer                       ,
    ss_hdemo_sk               integer                       ,
    ss_addr_sk                integer                       ,
    ss_store_sk               integer                       ,
    ss_promo_sk               integer                       ,
    ss_ticket_number          integer               not null,
    ss_quantity               integer                       ,
    ss_wholesale_cost         decimal(7,2)                  ,
    ss_list_price             decimal(7,2)                  ,
    ss_sales_price            decimal(7,2)                  ,
    ss_ext_discount_amt       decimal(7,2)                  ,
    ss_ext_sales_price        decimal(7,2)                  ,
    ss_ext_wholesale_cost     decimal(7,2)                  ,
    ss_ext_list_price         decimal(7,2)                  ,
    ss_ext_tax                decimal(7,2)                  ,
    ss_coupon_amt             decimal(7,2)                  ,
    ss_net_paid               decimal(7,2)                  ,
    ss_net_paid_inc_tax       decimal(7,2)                  ,
    ss_net_profit             decimal(7,2)                  ,
    primary key (ss_item_sk, ss_ticket_number)
);
create table store_returns
(
    sr_returned_date_sk       integer                       ,
    sr_return_time_sk         integer                       ,
    sr_item_sk                integer               not null,
    sr_customer_sk            integer                       ,
    sr_cdemo_sk               integer                       ,
    sr_hdemo_sk               integer                       ,
    sr_addr_sk                integer                       ,
    sr_store_sk               integer                       ,
    sr_reason_sk              integer                       ,
    sr_ticket_number          integer               not null,
    sr_return_quantity        integer                       ,
    sr_return_amt             decimal(7,2)                  ,
    sr_return_tax             decimal(7,2)                  ,
    sr_return_amt_inc_tax     decimal(7,2)                  ,
    sr_fee                    decimal(7,2)                  ,
    sr_return_ship_cost       decimal(7,2)                  ,
    sr_refunded_cash          decimal(7,2)                  ,
    sr_reversed_charge        decimal(7,2)                  ,
    sr_store_credit           decimal(7,2)                  ,
    sr_net_loss               decimal(7,2)                  ,
    primary key (sr_item_sk, sr_ticket_number)
);
create table customer
(
    c_customer_sk             integer               not null,
    c_customer_id             char(16)              not null,
    c_current_cdemo_sk        integer                       ,
    c_current_hdemo_sk        integer                       ,
    c_current_addr_sk         integer                       ,
    c_first_shipto_date_sk    integer                       ,
    c_first_sales_date_sk     integer                       ,
    c_salutation              char(10)                      ,
    c_first_name              char(20)                      ,
    c_last_name               char(30)                      ,
    c_preferred_cust_flag     char(1)                       ,
    c_birth_day               integer                       ,
    c_birth_month             integer                       ,
    c_birth_year              integer                       ,
    c_birth_country           varchar(20)                   ,
    c_login                   char(13)                      ,
    c_email_address           char(50)                      ,
    c_last_review_date        char(10)                      ,
    primary key (c_customer_sk)
);
create table promotion
(
    p_promo_sk                integer               not null,
    p_promo_id                char(16)              not null,
    p_start_date_sk           integer                       ,
    p_end_date_sk             integer                       ,
    p_item_sk                 integer                       ,
    p_cost                    decimal(15,2)                 ,
    p_response_target         integer                       ,
    p_promo_name              char(50)                      ,
    p_channel_dmail           char(1)                       ,
    p_channel_email           char(1)                       ,
    p_channel_catalog         char(1)                       ,
    p_channel_tv              char(1)                       ,
    p_channel_radio           char(1)                       ,
    p_channel_press           char(1)                       ,
    p_channel_event           char(1)                       ,
    p_channel_demo            char(1)                       ,
    p_channel_details         varchar(100)                  ,
    p_purpose                 char(15)                      ,
    p_discount_active         char(1)                       ,
    primary key (p_promo_sk)
);
create table customer_address
(
    ca_address_sk             integer               not null,
    ca_address_id             char(16)              not null,
    ca_street_number          char(10)                      ,
    ca_street_name            varchar(60)                   ,
    ca_street_type            char(15)                      ,
    ca_suite_number           char(10)                      ,
    ca_city                   varchar(60)                   ,
    ca_county                 varchar(30)                   ,
    ca_state                  char(2)                       ,
    ca_zip                    char(10)                      ,
    ca_country                varchar(20)                   ,
    ca_gmt_offset             decimal(5,2)                  ,
    ca_location_type          char(20)                      ,
    primary key (ca_address_sk)
);
create table item
(
    i_item_sk                 integer               not null,
    i_item_id                 char(16)              not null,
    i_rec_start_date          date                          ,
    i_rec_end_date            date                          ,
    i_item_desc               varchar(200)                  ,
    i_current_price           decimal(7,2)                  ,
    i_wholesale_cost          decimal(7,2)                  ,
    i_brand_id                integer                       ,
    i_brand                   char(50)                      ,
    i_class_id                integer                       ,
    i_class                   char(50)                      ,
    i_category_id             integer                       ,
    i_category                char(50)                      ,
    i_manufact_id             integer                       ,
    i_manufact                char(50)                      ,
    i_size                    char(20)                      ,
    i_formulation             char(20)                      ,
    i_color                   char(20)                      ,
    i_units                   char(10)                      ,
    i_container               char(10)                      ,
    i_manager_id              integer                       ,
    i_product_name            char(50)                      ,
    primary key (i_item_sk)
);
explain
select i_product_name product_name
,i_item_sk item_sk
,s_store_name store_name
,s_zip store_zip
,ad2.ca_street_number c_street_number
,ad2.ca_street_name c_street_name
,ad2.ca_city c_city
,ad2.ca_zip c_zip
,count(*) cnt
,sum(ss_wholesale_cost) s1
,sum(ss_list_price) s2
,sum(ss_coupon_amt) s3
FROM   store_sales
,store_returns
,store
,customer
,promotion
,customer_address ad2
,item
WHERE  ss_store_sk = s_store_sk AND
ss_customer_sk = c_customer_sk AND
ss_item_sk = i_item_sk and
ss_item_sk = sr_item_sk and
ss_ticket_number = sr_ticket_number and
c_current_addr_sk = ad2.ca_address_sk and
ss_promo_sk = p_promo_sk and
i_color in ('maroon','burnished','dim','steel','navajo','chocolate') and
i_current_price between 35 and 35 + 10 and
i_current_price between 35 + 1 and 35 + 15
group by i_product_name
,i_item_sk
,s_store_name
,s_zip
,ad2.ca_street_number
,ad2.ca_street_name
,ad2.ca_city
,ad2.ca_zip
;

img


Join顺序的Hint

功能描述

指明join的顺序,包括内外表顺序。

语法格式

  • 仅指定join顺序,不指定内外表顺序。

    leading(join_table_list)
  • 同时指定join顺序和内外表顺序,内外表顺序仅在最外层生效。

    leading((join_table_list))

参数说明

join_table_list为表示表join顺序的hint字符串,可以包含当前层的任意个表(别名),或对于子查询提升的场景,也可以包含子查询的hint别名,同时任意表可以使用括号指定优先级,表之间使用空格分隔。

img 须知: 表只能用单个字符串表示,不能带schema。 表如果存在别名,需要优先使用别名来表示该表。

join table list中指定的表需要满足以下要求,否则会报语义错误。

  • list中的表必须在当前层或提升的子查询中存在。
  • list中的表在当前层或提升的子查询中必须是唯一的。如果不唯一,需要使用不同的别名进行区分。
  • 同一个表只能在list里出现一次。
  • 如果表存在别名,则list中的表需要使用别名。

例如:

leading(t1 t2 t3 t4 t5)表示: t1,t2,t3,t4,t5先join,五表join顺序及内外表不限。

leading((t1 t2 t3 t4 t5))表示: t1和t2先join,t2做内表;再和t3 join,t3做内表;再和t4 join,t4做内表;再和t5 join,t5做内表。

leading(t1 (t2 t3 t4) t5)表示: t2,t3,t4先join,内外表不限;再和t1,t5 join,内外表不限。

leading((t1 (t2 t3 t4) t5))表示: t2,t3,t4先join,内外表不限;在最外层,t1再和t2,t3,t4的join表join,t1为外表,再和t5 join,t5为内表。

leading((t1 (t2 t3) t4 t5)) leading((t3 t2))表示: t2,t3先join,t2做内表;然后再和t1 join,t2,t3的join表做内表;然后再依次跟t4,t5做join,t4,t5做内表。

示例

示例中原语句使用如下hint:

explain
select /*+ leading((((((store_sales store) promotion) item) customer) ad2) store_returns) leading((store store_sales))*/ i_product_name product_name ...

该hint表示: 表之间的join关系是: store_sales和store先join,store_sales做内表,然后依次跟promotion, item, customer, ad2, store_returns做join。生成计划如下所示:

img


Join方式的Hint

功能描述

指明Join使用的方法,可以为Nested Loop,Hash Join和Merge Join。

语法格式

[no] nestloop|hashjoin|mergejoin(table_list)

参数说明

  • no表示hint的join方式不使用。
  • table_list为表示hint表集合的字符串,该字符串中的表与join_table_list相同,只是中间不允许出现括号指定join的优先级。

例如:

no nestloop(t1 t2 t3)表示: 生成t1,t2,t3三表连接计划时,不使用nestloop。三表连接计划可能是t2 t3先join,再跟t1 join,或t1 t2先join,再跟t3 join。此hint只hint最后一次join的join方式,对于两表连接的方法不hint。如果需要,可以单独指定,例如: 任意表均不允许nestloop连接,且希望t2 t3先join,则增加hint: no nestloop(t2 t3)。

示例

示例中原语句使用如下hint:

explain
select /*+ nestloop(store_sales store_returns item) */ i_product_name product_name ...

该hint表示: 生成store_sales,store_returns和item三表的结果集时,最后的两表关联使用nestloop。生成计划如下所示:

img


行数的Hint

功能描述

指明中间结果集的大小,支持绝对值和相对值的hint。

语法格式

rows(table_list #|+|-|* const)

参数说明

  • #+-,*****,进行行数估算hint的四种操作符号。#表示直接使用后面的行数进行hint。+,-,*表示对原来估算的行数进行加、减、乘操作,运算后的行数最小值为1行。table_list为hint对应的单表或多表join结果集,与Join方式的Hint中table_list相同。
  • const可以是任意非负数,支持科学计数法。

例如:

rows(t1 #5)表示: 指定t1表的结果集为5行。

rows(t1 t2 t3 *1000)表示: 指定t1, t2, t3 join完的结果集的行数乘以1000。

建议

  • 推荐使用两个表的hint。对于两个表的采用*操作符的hint,只要两个表出现在join的两端,都会触发hint。例如: 设置hint为rows(t1 t2 * 3),对于(t1 t3 t4)和(t2 t5 t6)join时,由于t1和t2出现在join的两端,所以其join的结果集也会应用该hint规则乘以3。
  • rows hint支持在单表、多表、function table及subquery scan table的结果集上指定hint。

示例

示例中原语句使用如下hint:

explain
select /*+ rows(store_sales store_returns *50) */ i_product_name product_name ...

该hint表示: store_sales,store_returns关联的结果集估算行数在原估算行数基础上乘以50。生成计划如下所示:

img


Scan方式的Hint

功能描述

指明scan使用的方法,可以是tablescan、indexscan和indexonlyscan。

语法格式

[no] tablescan|indexscan|indexonlyscan(table [index])

参数说明

  • no表示hint的scan方式不使用。
  • table表示hint指定的表,只能指定一个表,如果表存在别名应优先使用别名进行hint。
  • index表示使用indexscan或indexonlyscan的hint时,指定的索引名称,当前只能指定一个。

img 说明: 对于indexscan或indexonlyscan,只有hint的索引属于hint的表时,才能使用该hint。 scan hint支持在行列存表、obs表、子查询表上指定。

示例

为了hint使用索引扫描,需要首先在表item的i_item_sk列上创建索引,名称为i。

create index i on item(i_item_sk);

示例中原语句使用如下hint:

explain
select /*+ indexscan(item i) */ i_product_name product_name ...

该hint表示: item表使用索引i进行扫描。生成计划如下所示:

img


INDEX HINTS

注意事项

兼容限制:

  • 功能仅在B兼容模式下生效。
  • 目前仅支持MySql数据库中完整语法的部分语法及功能。
  • 语法仅在查询语句中使用此功能时生效。

功能描述

为指定的表在扫描时显示的指定期望使用的索引名称。

  • 使用USE INDEX指定的索引,会在扫描时综合考虑使用此索引扫描的代价和顺序扫描的代价,会选择代价更低的使用。
  • 使用FORCE INDEX指定的索引,如果可以使用索引扫描,会在扫描时强制使用此索引进行扫描。
  • FORCE INDEX和USE INDEX不能同时作用在同一张表中。
  • 多个index_hint连用等价index_list中写多个索引名字。

语法格式

tbl_name [ partition_clause ] [ [ AS ] alias ] [ index_hint_list ]

index_hint_list:
    index_hint [ index_hint ]
index_hint:
    USE {INDEX | KEY} ( [ index_list ] )
  | FORCE { INDEX | KEY } ( index_list )
index_list:
    index_name [ , index_name ] ...

参数说明

  • index_list

    索引的名称,使用逗号分隔。

  • tbl_name

    泛指一个表名。

示例

MogDB=# explain (costs off,verbose true  )select * from db_1097149_tb force key (index_1097149_4) where col2= 3 and col4 = 'a';
                        QUERY PLAN                        
----------------------------------------------------------
 Index Scan using index_1097149_4 on public.db_1097149_tb
   Output: col1, col2, col3, col4
   Index Cond: ((db_1097149_tb.col4)::text = 'a'::text)
   Filter: (db_1097149_tb.col2 = 3)
(4 rows)

子链接块名的hint

功能描述

指明子链接块的名称。

语法格式

blockname (table)

参数说明

  • table表示为该子链接块hint的别名的名称。

img 说明:

  • blockname hint仅在对应的子链接块没有提升时才会被上层查询使用。目前支持的子链接提升包括IN子链接提升、EXISTS子链接提升和包含Agg等值相关子链接提升。该hint通常会和前面章节提到的hint联合使用。
  • 对于FROM关键字后的子查询,则需要使用子查询的别名进行hint,blockname hint不会被用到。
  • 如果子链接中含有多个表,则提升后这些表可与外层表以任意优化顺序连接,hint也不会被用到。

示例

explain select /*+nestloop(store_sales tt) */ * from store_sales where ss_item_sk in (select /*+blockname(tt)*/ i_item_sk from item group by 1);

该hint表示: 子链接的别名为tt,提升后与上层的store_sales表关联时使用nestloop。生成计划如下所示:

img


Hint的错误、冲突及告警

Plan Hint的结果会体现在计划的变化上,可以通过explain来查看变化。

Hint中的错误不会影响语句的执行,只是不能生效,该错误会根据语句类型以不同方式提示用户。对于explain语句,hint的错误会以warning形式显示在界面上,对于非explain语句,会以debug1级别日志显示在日志中,关键字为PLANHINT。

hint的错误分为以下类型:

  • 语法错误

    语法规则树归约失败,会报错,指出出错的位置。

    例如: hint关键字错误,leading hint或join hint指定2个表以下,其它hint未指定表等。一旦发现语法错误,则立即终止hint的解析,所以此时只有错误前面的解析完的hint有效。

    例如:

    leading((t1 t2)) nestloop(t1) rows(t1 t2 #10)

    nestloop(t1)存在语法错误,则终止解析,可用hint只有之前解析的leading((t1 t2))。

  • 语义错误

    • 表不存在,存在多个,或在leading或join中出现多次,均会报语义错误。
    • scanhint中的index不存在,会报语义错误。
    • 另外,如果子查询提升后,同一层出现多个名称相同的表,且其中某个表需要被hint,hint会存在歧义,无法使用,需要为相同表增加别名规避。
  • hint重复或冲突

    如果存在hint重复或冲突,只有第一个hint生效,其它hint均会失效,会给出提示。

    • hint重复是指,hint的方法及表名均相同。例如: nestloop(t1 t2) nestloop(t1 t2)。

    • hint冲突是指,table list一样的hint,存在不一样的hint,hint的冲突仅对于每一类hint方法检测冲突。

      例如: nestloop (t1 t2) hashjoin (t1 t2),则后面与前面冲突,此时hashjoin的hint失效。注意:nestloop(t1 t2)和no mergejoin(t1 t2)不冲突。

      img 须知: leading hint中的多个表会进行拆解。例如: leading ((t1 t2 t3))会拆解成: leading((t1 t2)) leading(((t1 t2) t3)),此时如果存在leading((t2 t1)),则两者冲突,后面的会被丢弃。(例外: 指定内外表的hint若与不指定内外表的hint重复,则始终丢弃不指定内外表的hint。)

  • 子链接提升后hint失效

    子链接提升后的hint失效,会给出提示。通常出现在子链接中存在多个表连接的场景。提升后,子链接中的多个表不再作为一个整体出现在join中。

  • 列类型不支持重分布

    • 对于skew hint来说,目的是为了进行重分布时的调优,所以当hint列的类型不支持重分布时,hint将无效。
  • hint未被使用

    • 非等值join使用hashjoin hint或mergejoin hint。
    • 不包含索引的表使用indexscan hint或indexonlyscan hint。
    • 通常只有在索引列上使用过滤条件才会生成相应的索引路径,全表扫描将不会使用索引,因此使用indexscan hint或indexonlyscan hint将不会使用。
    • indexonlyscan只有输出列仅包含索引列才会使用,否则指定时hint不会被使用。
    • 多个表存在等值连接时,仅尝试有等值连接条件的表的连接,此时没有关联条件的表之间的路径将不会生成,所以指定相应的leading,join,rows hint将不使用,例如: t1 t2 t3表join,t1和t2, t2和t3有等值连接条件,则t1和t3不会优先连接,leading(t1 t3)不会被使用。
    • 生成stream计划时,如果表的分布列与join列相同,则不会生成redistribute的计划;如果不同,且另一表分布列与join列相同,只能生成redistribute的计划,不会生成broadcast的计划,指定相应的hint则不会被使用。
    • 如果子链接未被提升,则blockname hint不会被使用。
    • 对于skew hint,hint未被使用可能由于:
      • 计划中不需要进行重分布。
      • hint指定的列为包含分布键。
      • hint指定倾斜信息有误或不完整,如对于join优化未指定值。
      • 倾斜优化的GUC参数处于关闭状态。

优化器GUC参数的Hint

功能描述

设置本次查询执行内生效的查询优化相关GUC参数。hint的推荐使用场景可以参考各guc参数的说明,此处不作赘述。

语法格式

set(param value)

参数说明

  • param表示参数名。

  • value表示参数的取值。

  • 目前支持使用Hint设置生效的参数有

    • 布尔类:

      enable_bitmapscan, enable_hashagg,enable_hashjoin, enable_indexscan,enable_indexonlyscan, enable_material,enable_mergejoin, enable_nestloop,enable_index_nestloop, enable_seqscan,enable_sort, enable_tidscan,partition_iterator_elimination,partition_page_estimation,enable_functional_dependency,var_eq_const_selectivity,enable_inner_unique_opt, adaptive_hashagg_allow_spill, enable_adaptive_hashagg, enable_bloom_filter, enable_constraint_optimization, enable_force_vector_engine, enable_partitionwise, enable_sonic_hashagg, enable_sonic_hashjoin, enable_sonic_optspill, enable_vector_engine, force_bitmapand, geqo

    • 整形类:

      query_dop, adaptive_hashagg_min_rows, cost_param, from_collapse_limit, geqo_effort, geqo_generations, geqo_pool_size, geqo_threshold, hashagg_table_size, join_collapse_limit, plan_mode_seed, query_max_mem, query_mem, work_mem

    • 浮点类:

      cost_weight_index, default_limit_rows, seq_page_cost, random_page_cost, cpu_tuple_cost, cpu_index_tuple_cost, cpu_operator_cost, effective_cache_size, adaptive_hashagg_reduce_ratio_threshold, allocate_mem_cost, cursor_tuple_fraction, geqo_seed, geqo_selection_bias, hash_agg_total_cost_ratio, hash_join_total_cost_ratio, merge_join_total_cost_ratio, nestloop_total_cost_ratio, sort_agg_total_cost_ratio

    • 枚举类型:

      try_vector_engine_strategy, constraint_exclusion, rewrite_rule

img 说明:

  • 设置不在白名单中的参数,参数取值不合法,或hint语法错误时,不会影响查询执行的正确性。使用explain(verbose on)执行可以看到hint解析错误的报错提示。
  • GUC参数的hint只在最外层查询生效——子查询内的GUC参数hint不生效。
  • 视图定义内的GUC参数hint不生效。
  • CREATE TABLE … AS … 查询最外层的GUC参数hint可以生效。

Custom Plan和Generic Plan选择的Hint

功能描述

对于以PBE方式执行的查询语句和DML语句,优化器会基于规则、代价、参数等因素选择生成Custom Plan或Generic Plan执行。用户可以通过use_cplan/use_gplan的hint指定使用哪种计划执行方式。

语法格式

  • 指定使用Custom Plan:

    use_cplan
  • 指定使用Generic Plan:

    use_gplan

img 说明:

  • 对于非PBE方式执行的SQL语句,设置本hint不会影响执行方式。
  • 本Hint的优先级仅高于基于代价的选择和plan_cache_mode参数,即plan_cache_mode无法强制选择执行方式的语句本hint也无法生效。

示例

强制使用Custom Plan

create table t (a int, b int, c int);
prepare p as select /*+ use_cplan */ * from t where a = $1;
explain execute p(1);

计划如下。可以看到过滤条件为入参的实际值,即此计划为Custom Plan。

img

强制使用Generic Plan

deallocate p;
prepare p as select /*+ use_gplan */ * from t where a = $1;
explain execute p(1);

计划如下。可以看到过滤条件为待填充的入参,即此计划为Generic Plan。

img

指定子查询不展开的Hint

功能描述

数据库在对查询进行逻辑优化时通常会将可以提升的子查询提升到上层来避免嵌套执行,但对于某些本身选择率较低且可以使用索引过滤访问页面的子查询,嵌套执行不会导致性能下降过多,而提升之后扩大了查询路径的搜索范围,可能导致性能变差。对于此类情况,可以使用no_expand Hint进行调试。大多数情况下不建议使用此hint。

语法格式

no_expand

示例

正常的查询执行

explain select * from t1 where t1.a in (select t2.a from t2);

计划

img

加入no_expand

explain select * from t1 where t1.a in (select /*+ no_expand*/ t2.a from t2);

计划

img

指定不使用全局计划缓存的Hint

功能描述

全局计划缓存打开时,可以通过no_gpc Hint来强制单个查询语句不在全局共享计划缓存,只保留会话生命周期的计划缓存。

语法格式

no_gpc

img 说明: 本参数仅在enable_global_plancache=on时对PBE执行的语句生效。

示例

img

dbe_perf.global_plancache_status视图中无结果即没有计划被全局缓存。

同层参数化路径的Hint

功能描述

通过predpush_same_level Hint来指定同层表或物化视图之间参数化路径生成。

语法格式

predpush_same_level(src, dest)
predpush_same_level(src1 src2 ..., dest)

说明: 本参数仅在rewrite_rule中的predpushforce选项打开时生效。

示例

准备参数和表及索引:

MogDB=# set rewrite_rule = 'predpushforce';
SET
MogDB=# create table t1(a int, b int);
CREATE TABLE
MogDB=# create table t2(a int, b int);
CREATE TABLE
MogDB=# create index idx1 on t1(a);
CREATE INDEX
MogDB=# create index idx2 on t2(a);
CREATE INDEX

执行语句查看计划:

MogDB=# explain select * from t1, t2 where t1.a = t2.a;
                            QUERY PLAN
------------------------------------------------------------------
 Hash Join  (cost=27.50..56.25 rows=1000 width=16)
   Hash Cond: (t1.a = t2.a)
   ->  Seq Scan on t1  (cost=0.00..15.00 rows=1000 width=8)
   ->  Hash  (cost=15.00..15.00 rows=1000 width=8)
         ->  Seq Scan on t2  (cost=0.00..15.00 rows=1000 width=8)
(5 rows)

可以看到t1.a = t2.a条件过滤在Join上面,此时可以通过predpush_same_level(t1, t2)将条件下推至t2的扫描算子上:

MogDB=# explain select /*+predpush_same_level(t1, t2)*/ * from t1, t2 where t1.a = t2.a;
                             QUERY PLAN
---------------------------------------------------------------------
 Nested Loop  (cost=0.00..335.00 rows=1000 width=16)
   ->  Seq Scan on t1  (cost=0.00..15.00 rows=1000 width=8)
   ->  Index Scan using idx2 on t2  (cost=0.00..0.31 rows=1 width=8)
         Index Cond: (a = t1.a)
(4 rows)

须知:

  • predpush_same_level可以指定多个src,但是所有的src必须在同一个条件中。
  • 如果指定的src和dest条件不存在,或该条件不符合参数化路径要求,则本hint不生效。

不同层参数化路径的Hint

功能描述

通过predpush Hint来指定不同层表或物化视图之间参数化路径生成。

语法格式

predpush(src, dest)
predpush(src1 src2 ..., dest)

说明:本参数仅在rewrite_rule中的predpushforce选项打开时生效。

示例

准备参数和表及索引:

MogDB=# set rewrite_rule = 'predpushforce';
SET
MogDB=# create table t1(a int, b int);
CREATE TABLE
MogDB=# create table t2(a int, b int);
CREATE TABLE
MogDB=# create index idx1 on t1(a);
CREATE INDEX
MogDB=# create index idx2 on t2(a);
CREATE INDEX

执行语句查看计划:

MogDB=# explain select * from t1,(select a, b, min(a) from t2 group by 1,2) ts where t1.a = ts.a;
                               QUERY PLAN
------------------------------------------------------------------------
 Hash Join  (cost=55.38..120.60 rows=2568 width=20)
   Hash Cond: (t1.a = t2.a)
   ->  Seq Scan on t1  (cost=0.00..31.49 rows=2149 width=8)
   ->  Hash  (cost=52.39..52.39 rows=239 width=12)
         ->  HashAggregate  (cost=47.61..50.00 rows=239 width=12)
               Group By Key: t2.a, t2.b
               ->  Seq Scan on t2  (cost=0.00..31.49 rows=2149 width=8)
(7 rows)

可以看到t1.a = t2.a条件过滤在Join上面,此时可以通过predpush(t1, ts)将条件下推至t2的扫描算子上:

MogDB=# explain select /*+ predpush(t1, ts) */ * from t1,(select a, b, min(a) from t2 group by 1,2) ts where t1.a = ts.a;
                                   QUERY PLAN
--------------------------------------------------------------------------------
 Nested Loop  (cost=15.09..32586.69 rows=21 width=20)
   ->  Seq Scan on t1  (cost=0.00..31.49 rows=2149 width=8)
   ->  HashAggregate  (cost=15.09..15.11 rows=2 width=12)
         Group By Key: t2.a, t2.b
         ->  Bitmap Heap Scan on t2  (cost=4.34..15.01 rows=11 width=8)
               Recheck Cond: (t1.a = a)
               ->  Bitmap Index Scan on idx2  (cost=0.00..4.33 rows=11 width=0)
                     Index Cond: (t1.a = a)
(8 rows)

将部分Error降级为Warning的Hint

功能描述

指定执行INSERT、UPDATE语句时可将部分Error降级为Warning,且不影响语句执行完成的hint。

该hint不支持列存表,无法在列存表中生效。

img 注意:

与其他hint不同,此hint仅影响执行器遇到部分Error时的处理方式,不会对执行计划有任何影响。

使用该hint时,Error会被降级的场景有:

  • 违反非空约束时

    若执行的SQL语句违反了表的非空约束,使用此hint可将Error降级为Warning,并根据GUC参数sql_ignore_strategy的值采用以下策略的一种继续执行:

    • sql_ignore_startegy为ignore_null时,忽略违反非空约束的行的INSERT/UPDATE操作,并继续执行剩余数据操作。

    • sql_ignore_startegy为overwrite_null时,将违反约束的null值覆写为目标类型的默认值,并继续执行剩余数据操作。

      img 说明:

      GUC参数sql_ignore_strategy相关信息请参考sql_ignore_strategy

  • 违反唯一约束时

    若执行的SQL语句违反了表的唯一约束,使用此hint可将Error降级为Warning,忽略违反约束的行的INSERT/UPDATE操作,并继续执行剩余数据操作。

  • 分区表无法匹配到合法分区时

    在对分区表进行INSERT/UPDATE操作时,若某行数据无法匹配到表格的合法分区,使用此hint可将Error降级为Warning,忽略该行操作,并继续执行剩余数据操作。

  • 更新/插入值向目标列类型转换失败时

    执行INSERT/UPDATE语句时,若发现新值与目标列类型不匹配,使用此hint可将Error降级为Warning,并根据新值与目标列的具体类型采取以下策略的一种继续执行:

    • 当新值类型与列类型同为数值类型时:

      若新值在列类型的范围内,则直接进行插入/更新;若新值在列类型范围外,则以列类型的最大/最小值替代。

    • 当新值类型与列类型同为字符串类型时:

      若新值长度在列类型限定范围内,则以直接进行插入/更新;若新值长度在列类型的限定范围外,则保留列类型长度限制的前n个字符。

    • 若遇到新值类型与列类型不可转换时:

      插入/更新列类型的默认值。

语法格式

ignore_error

示例

为使用ignore_error hint,需要创建B兼容模式的数据库,名称为db_ignore。

create database db_ignore dbcompatibility 'B';
\c db_ignore
  • 忽略非空约束
db_ignore=# create table t_not_null(num int not null);
CREATE TABLE
-- 采用忽略策略
db_ignore=# set sql_ignore_strategy = 'ignore_null';
SET
db_ignore=# insert /*+ ignore_error */ into t_not_null values(null), (1);
WARNING:  null value in column "num" violates not-null constraint
DETAIL:  Failing row contains (null).
INSERT 0 1
db_ignore=# select * from t_not_null ;
 num 
-----
   1
(1 row)

db_ignore=# update /*+ ignore_error */ t_not_null set num = null where num = 1;
WARNING:  null value in column "num" violates not-null constraint
DETAIL:  Failing row contains (null).
UPDATE 0
db_ignore=# select * from t_not_null ;
 num 
-----
   1
(1 row)


-- 采用覆写策略
db_ignore=# delete from t_not_null;
db_ignore=# set sql_ignore_strategy = 'overwrite_null';
SET
db_ignore=# insert /*+ ignore_error */ into t_not_null values(null), (1);
WARNING:  null value in column "num" violates not-null constraint
DETAIL:  Failing row contains (null).
INSERT 0 2
db_ignore=# select * from t_not_null ;
 num 
-----
   0
   1
(2 rows)

db_ignore=# update /*+ ignore_error */ t_not_null set num = null where num = 1;
WARNING:  null value in column "num" violates not-null constraint
DETAIL:  Failing row contains (null).
UPDATE 1
db_ignore=# select * from t_not_null ;
 num 
-----
   0
   0
(2 rows)
  • 忽略唯一约束
db_ignore=# create table t_unique(num int unique);
NOTICE:  CREATE TABLE / UNIQUE will create implicit index "t_unique_num_key" for table "t_unique"
CREATE TABLE
db_ignore=# insert into t_unique values(1);
INSERT 0 1
db_ignore=# insert /*+ ignore_error */ into t_unique values(1),(2);
WARNING:  duplicate key value violates unique constraint in table "t_unique"
INSERT 0 1
db_ignore=# select * from t_unique;
 num 
-----
   1
   2
(2 rows)

db_ignore=# update /*+ ignore_error */ t_unique set num = 1 where num = 2;
WARNING:  duplicate key value violates unique constraint in table "t_unique"
UPDATE 0
db_ignore=# select * from t_unique ;
 num 
-----
   1
   2
(2 rows)
  • 忽略分区表无法匹配到合法分区
db_ignore=# CREATE TABLE t_ignore
db_ignore-# (
db_ignore(#     col1 integer NOT NULL,
db_ignore(#     col2 character varying(60)
db_ignore(# ) WITH(segment = on) PARTITION BY RANGE (col1)
db_ignore-# (
db_ignore(#     PARTITION P1 VALUES LESS THAN(5000),
db_ignore(#     PARTITION P2 VALUES LESS THAN(10000),
db_ignore(#     PARTITION P3 VALUES LESS THAN(15000)
db_ignore(# );
CREATE TABLE
db_ignore=# insert /*+ ignore_error */ into t_ignore values(20000);
WARNING:  inserted partition key does not map to any table partition
INSERT 0 0
db_ignore=# select * from t_ignore ;
 col1 | col2 
------+------
(0 rows)

db_ignore=# insert into t_ignore values(3000);
INSERT 0 1
db_ignore=# select * from t_ignore ;
 col1 | col2 
------+------
 3000 | 
(1 row)
db_ignore=# update /*+ ignore_error */ t_ignore set col1 = 20000 where col1 = 3000;
WARNING:  fail to update partitioned table "t_ignore".new tuple does not map to any table partition.
UPDATE 0
db_ignore=# select * from t_ignore ;
 col1 | col2 
------+------
 3000 | 
(1 row)
  • 更新/插入值向目标列类型转换失败
-- 当新值类型与列类型同为数值类型
db_ignore=# create table t_tinyint(num tinyint);
CREATE TABLE
db_ignore=# insert /*+ ignore_error */ into t_tinyint values(10000);
WARNING:  tinyint out of range
CONTEXT:  referenced column: num
INSERT 0 1
db_ignore=# select * from t_tinyint;
 num 
-----
 255
(1 row)

-- 当新值类型与列类型同为字符类型时
db_ignore=# create table t_varchar5(content varchar(5));
CREATE TABLE
db_ignore=# insert /*+ ignore_error */ into t_varchar5 values('abcdefghi');
WARNING:  value too long for type character varying(5)
CONTEXT:  referenced column: content
INSERT 0 1
db_ignore=# select * from t_varchar5 ;
 content 
---------
 abcde
(1 row)
Copyright © 2011-2024 www.enmotech.com All rights reserved.