📣

TiDB Cloud Serverless 现已更名为
Starter
!此页面由 AI 自动翻译,英文原文请见
此处。

优化器 Hint

TiDB 支持优化器 Hint,其语法基于 MySQL 5.7 引入的类似注释的语法。例如,常见的语法之一是 /*+ HINT_NAME([t1_name [, t2_name] ...]) */。当 TiDB 优化器选择了次优的查询计划时,建议使用优化器 Hint。

如果你遇到 Hint 不生效的情况,请参见排查 Hint 不生效的常见问题

语法

优化器 Hint 不区分大小写,并且需要写在 SQL 语句中 SELECTINSERTUPDATEDELETE 关键字后面的 /*+ ... */ 注释中。

可以通过逗号分隔指定多个 Hint。例如,以下查询使用了三种不同的 Hint:

SELECT /*+ USE_INDEX(t1, idx1), HASH_AGG(), HASH_JOIN(t1) */ count(*) FROM t t1, t t2 WHERE t1.a = t2.b;

优化器 Hint 对查询执行计划的影响可以通过 EXPLAINEXPLAIN ANALYZE 的输出结果观察到。

错误或不完整的 Hint 不会导致语句报错。这是因为 Hint 仅作为对查询执行的建议语义。同样地,如果 Hint 不适用,TiDB 最多只会返回一个警告。

目前,TiDB 支持两类 Hint,作用范围不同。第一类 Hint 在查询块范围内生效,例如 /*+ HASH_AGG() */;第二类 Hint 在整个查询中生效,例如 /*+ MEMORY_QUOTA(1024 MB)*/

一个语句中的每个查询或子查询对应一个不同的查询块,每个查询块都有自己的名称。例如:

SELECT * FROM (SELECT * FROM t) t1, (SELECT * FROM t) t2;

上述查询语句有三个查询块:最外层的 SELECT 对应第一个查询块,名称为 sel_1;两个 SELECT 子查询分别对应第二和第三个查询块,名称分别为 sel_2sel_3。数字的顺序根据 SELECT 从左到右出现的顺序决定。如果将第一个 SELECT 替换为 DELETEUPDATE,则对应的查询块名称为 del_1upd_1

查询块范围内生效的 Hint

此类 Hint 可以跟在任意 SELECTUPDATEDELETE 关键字后面。为了控制 Hint 的生效范围,可以在 Hint 中使用查询块的名称。你可以通过准确标识查询中的每个表(以防表名或别名重复)来明确 Hint 的参数。如果 Hint 中未指定查询块,则默认在当前块中生效。

例如:

SELECT /*+ HASH_JOIN(@sel_1 t1@sel_1, t3) */ * FROM (SELECT t1.a, t1.b FROM t t1, t t2 WHERE t1.a = t2.a) t1, t t3 WHERE t1.b = t3.b;

该 Hint 在 sel_1 查询块中生效,其参数为 sel_1 中的 t1t3 表(sel_2 也包含一个 t1 表)。

如上所述,你可以通过以下方式在 Hint 中指定查询块名称:

  • 将查询块名称作为 Hint 的第一个参数,并用空格与其他参数分隔。除了 QB_NAME 外,本节列出的所有 Hint 还支持一个可选的隐藏参数 @QB_NAME。通过该参数,可以指定 Hint 的生效范围。
  • 在参数中的表名后追加 @QB_NAME,以显式指定该表属于哪个查询块。

QB_NAME

如果查询语句较为复杂,包含多层嵌套查询,某个查询块的 ID 和名称可能会被误识别。此时可以使用 QB_NAME Hint 进行辅助。

QB_NAME 表示查询块名称。你可以为查询块指定一个新名称。指定的 QB_NAME 和之前的默认名称都有效。例如:

SELECT /*+ QB_NAME(QB1) */ * FROM (SELECT * FROM t) t1, (SELECT * FROM t) t2;

该 Hint 指定了外层 SELECT 查询块的名称为 QB1,此时 QB1 和默认名称 sel_1 都是该查询块的有效名称。

MERGE_JOIN(t1_name [, tl_name ...])

MERGE_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表使用排序归并连接算法。通常,该算法内存消耗较少,但处理时间较长。如果数据量很大或系统内存不足,建议使用该 Hint。例如:

select /*+ MERGE_JOIN(t1, t2) */ * from t1, t2 where t1.id = t2.id;

NO_MERGE_JOIN(t1_name [, tl_name ...])

NO_MERGE_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表不使用排序归并连接算法。例如:

SELECT /*+ NO_MERGE_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id;

INL_JOIN(t1_name [, tl_name ...])

INL_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表使用索引嵌套循环连接算法。在某些场景下,该算法可能消耗更少的系统资源、处理时间更短,但在其他场景下可能相反。如果外表经过 WHERE 条件过滤后结果集小于 10000 行,建议使用该 Hint。例如:

SELECT /*+ INL_JOIN(t1, t2) */ * FROM t1, t2, t3 WHERE t1.id = t2.id AND t2.id = t3.id;

在上述 SQL 语句中,INL_JOIN(t1, t2) Hint 告诉优化器对 t1t2 使用索引嵌套循环连接算法。注意,这并不意味着 t1t2 之间直接使用索引嵌套循环连接,而是表示 t1t2 分别与其他表(如 t3)使用该算法。

INL_JOIN() 中给定的参数是在生成查询计划时作为内表的候选表。例如,INL_JOIN(t1) 表示 TiDB 只考虑将 t1 作为内表生成查询计划。如果候选表有别名,必须使用别名作为 INL_JOIN() 的参数;如果没有别名,则使用表的原始名称。例如,在 select /*+ INL_JOIN(t1) */ * from t t1, t t2 where t1.a = t2.b; 查询中,必须使用 t 表的别名 t1t2,而不是 t 作为 INL_JOIN() 的参数。

NO_INDEX_JOIN(t1_name [, tl_name ...])

NO_INDEX_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表不使用索引嵌套循环连接算法。例如:

SELECT /*+ NO_INDEX_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id;

INL_HASH_JOIN

INL_HASH_JOIN(t1_name [, tl_name]) Hint 告诉优化器使用索引嵌套循环哈希连接算法。该算法的使用条件与索引嵌套循环连接算法相同。两者的区别在于,INL_JOIN 在被连接的内表上构建哈希表,而 INL_HASH_JOIN 在被连接的外表上构建哈希表。INL_HASH_JOIN 的内存使用有固定上限,而 INL_JOIN 的内存消耗取决于内表匹配的行数。

NO_INDEX_HASH_JOIN(t1_name [, tl_name ...])

NO_INDEX_HASH_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表不使用索引嵌套循环哈希连接算法。

INL_MERGE_JOIN

INL_MERGE_JOIN(t1_name [, tl_name]) Hint 告诉优化器对指定表使用索引嵌套循环归并连接算法。该算法的使用条件与索引嵌套循环连接算法相同。

NO_INDEX_MERGE_JOIN(t1_name [, tl_name ...])

NO_INDEX_MERGE_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表不使用索引嵌套循环归并连接算法。

HASH_JOIN(t1_name [, tl_name ...])

HASH_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表使用哈希连接算法。该算法允许查询并发多线程执行,处理速度更快,但内存消耗更大。例如:

select /*+ HASH_JOIN(t1, t2) */ * from t1, t2 where t1.id = t2.id;

NO_HASH_JOIN(t1_name [, tl_name ...])

NO_HASH_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表不使用哈希连接算法。例如:

SELECT /*+ NO_HASH_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id;

HASH_JOIN_BUILD(t1_name [, tl_name ...])

HASH_JOIN_BUILD(t1_name [, tl_name ...]) Hint 告诉优化器对指定表使用哈希连接算法,并将这些表作为构建端(Build side)。这样可以指定用哪些表构建哈希表。例如:

SELECT /*+ HASH_JOIN_BUILD(t1) */ * FROM t1, t2 WHERE t1.id = t2.id;

HASH_JOIN_PROBE(t1_name [, tl_name ...])

HASH_JOIN_PROBE(t1_name [, tl_name ...]) Hint 告诉优化器对指定表使用哈希连接算法,并将这些表作为探测端(Probe side)。这样可以指定用哪些表作为探测端执行哈希连接。例如:

SELECT /*+ HASH_JOIN_PROBE(t2) */ * FROM t1, t2 WHERE t1.id = t2.id;

SEMI_JOIN_REWRITE()

SEMI_JOIN_REWRITE() Hint 告诉优化器将半连接查询重写为普通连接查询。目前,该 Hint 仅对 EXISTS 子查询生效。

如果不使用该 Hint 进行重写,当执行计划选择哈希连接时,半连接查询只能用子查询构建哈希表。此时,如果子查询的结果比外层查询大,执行速度可能低于预期。

同样地,当执行计划选择索引连接时,半连接查询只能用外层查询作为驱动表。此时,如果子查询的结果比外层查询小,执行速度也可能低于预期。

使用 SEMI_JOIN_REWRITE() 进行重写后,优化器可以扩展选择范围,选择更优的执行计划。

-- 未使用 SEMI_JOIN_REWRITE() 重写查询。 EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT 1 FROM t1 WHERE t1.a = t.a);
+-----------------------------+---------+-----------+------------------------+---------------------------------------------------+ | id | estRows | task | access object | operator info | +-----------------------------+---------+-----------+------------------------+---------------------------------------------------+ | MergeJoin_9 | 7992.00 | root | | semi join, left key:test.t.a, right key:test.t1.a | | ├─IndexReader_25(Build) | 9990.00 | root | | index:IndexFullScan_24 | | │ └─IndexFullScan_24 | 9990.00 | cop[tikv] | table:t1, index:idx(a) | keep order:true, stats:pseudo | | └─IndexReader_23(Probe) | 9990.00 | root | | index:IndexFullScan_22 | | └─IndexFullScan_22 | 9990.00 | cop[tikv] | table:t, index:idx(a) | keep order:true, stats:pseudo | +-----------------------------+---------+-----------+------------------------+---------------------------------------------------+
-- 使用 SEMI_JOIN_REWRITE() 重写查询。 EXPLAIN SELECT * FROM t WHERE EXISTS (SELECT /*+ SEMI_JOIN_REWRITE() */ 1 FROM t1 WHERE t1.a = t.a);
+------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+ | IndexJoin_16 | 1.25 | root | | inner join, inner:IndexReader_15, outer key:test.t1.a, inner key:test.t.a, equal cond:eq(test.t1.a, test.t.a) | | ├─StreamAgg_39(Build) | 1.00 | root | | group by:test.t1.a, funcs:firstrow(test.t1.a)->test.t1.a | | │ └─IndexReader_34 | 1.00 | root | | index:IndexFullScan_33 | | │ └─IndexFullScan_33 | 1.00 | cop[tikv] | table:t1, index:idx(a) | keep order:true | | └─IndexReader_15(Probe) | 1.25 | root | | index:Selection_14 | | └─Selection_14 | 1.25 | cop[tikv] | | not(isnull(test.t.a)) | | └─IndexRangeScan_13 | 1.25 | cop[tikv] | table:t, index:idx(a) | range: decided by [eq(test.t.a, test.t1.a)], keep order:false, stats:pseudo | +------------------------------+---------+-----------+------------------------+---------------------------------------------------------------------------------------------------------------+

从上述示例可以看出,使用 SEMI_JOIN_REWRITE() Hint 后,TiDB 可以基于驱动表 t1 选择 IndexJoin 的执行方式。

SHUFFLE_JOIN(t1_name [, tl_name ...])

SHUFFLE_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表使用 Shuffle Join 算法。该 Hint 仅在 MPP 模式下生效。例如:

SELECT /*+ SHUFFLE_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id;

BROADCAST_JOIN(t1_name [, tl_name ...])

BROADCAST_JOIN(t1_name [, tl_name ...]) Hint 告诉优化器对指定表使用 Broadcast Join 算法。该 Hint 仅在 MPP 模式下生效。例如:

SELECT /*+ BROADCAST_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id = t2.id;

NO_DECORRELATE()

NO_DECORRELATE() Hint 告诉优化器不要尝试对指定查询块中的相关子查询进行去相关化。该 Hint 适用于包含相关列(即相关子查询)的 EXISTSINANYALLSOME 子查询和标量子查询。

当在查询块中使用该 Hint 时,优化器不会尝试对子查询与外层查询块之间的相关列进行去相关化,而是始终使用 Apply 算子执行查询。

默认情况下,TiDB 会尝试对相关子查询进行去相关化,以获得更高的执行效率。但在某些场景下,去相关化反而可能降低执行效率。此时可以使用该 Hint 手动告知优化器不要进行去相关化。例如:

create table t1(a int, b int); create table t2(a int, b int, index idx(b));
-- 未使用 NO_DECORRELATE()。 explain select * from t1 where t1.a < (select sum(t2.a) from t2 where t2.b = t1.b);
+----------------------------------+----------+-----------+---------------+--------------------------------------------------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +----------------------------------+----------+-----------+---------------+--------------------------------------------------------------------------------------------------------------+ | HashJoin_11 | 9990.00 | root | | inner join, equal:[eq(test.t1.b, test.t2.b)], other cond:lt(cast(test.t1.a, decimal(10,0) BINARY), Column#7) | | ├─HashAgg_23(Build) | 7992.00 | root | | group by:test.t2.b, funcs:sum(Column#8)->Column#7, funcs:firstrow(test.t2.b)->test.t2.b | | │ └─TableReader_24 | 7992.00 | root | | data:HashAgg_16 | | │ └─HashAgg_16 | 7992.00 | cop[tikv] | | group by:test.t2.b, funcs:sum(test.t2.a)->Column#8 | | │ └─Selection_22 | 9990.00 | cop[tikv] | | not(isnull(test.t2.b)) | | │ └─TableFullScan_21 | 10000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo | | └─TableReader_15(Probe) | 9990.00 | root | | data:Selection_14 | | └─Selection_14 | 9990.00 | cop[tikv] | | not(isnull(test.t1.b)) | | └─TableFullScan_13 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | +----------------------------------+----------+-----------+---------------+--------------------------------------------------------------------------------------------------------------+

从上述执行计划可以看出,优化器自动进行了去相关化。去相关化后的执行计划不再有 Apply 算子,而是将子查询与外层查询块之间的原相关列过滤条件(t2.b = t1.b)转化为普通的连接条件。

-- 使用 NO_DECORRELATE()。 explain select * from t1 where t1.a < (select /*+ NO_DECORRELATE() */ sum(t2.a) from t2 where t2.b = t1.b);
+------------------------------------------+-----------+-----------+------------------------+--------------------------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +------------------------------------------+-----------+-----------+------------------------+--------------------------------------------------------------------------------------+ | Projection_10 | 10000.00 | root | | test.t1.a, test.t1.b | | └─Apply_12 | 10000.00 | root | | CARTESIAN inner join, other cond:lt(cast(test.t1.a, decimal(10,0) BINARY), Column#7) | | ├─TableReader_14(Build) | 10000.00 | root | | data:TableFullScan_13 | | │ └─TableFullScan_13 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | | └─MaxOneRow_15(Probe) | 10000.00 | root | | | | └─StreamAgg_20 | 10000.00 | root | | funcs:sum(Column#14)->Column#7 | | └─Projection_45 | 100000.00 | root | | cast(test.t2.a, decimal(10,0) BINARY)->Column#14 | | └─IndexLookUp_44 | 100000.00 | root | | | | ├─IndexRangeScan_42(Build) | 100000.00 | cop[tikv] | table:t2, index:idx(b) | range: decided by [eq(test.t2.b, test.t1.b)], keep order:false, stats:pseudo | | └─TableRowIDScan_43(Probe) | 100000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo | +------------------------------------------+-----------+-----------+------------------------+--------------------------------------------------------------------------------------+

从上述执行计划可以看出,优化器未进行去相关化,执行计划中仍然包含 Apply 算子,相关列的过滤条件(t2.b = t1.b)依然作为访问 t2 表时的过滤条件。

HASH_AGG()

HASH_AGG() Hint 告诉优化器在指定查询块的所有聚合函数中使用哈希聚合算法。该算法允许查询并发多线程执行,处理速度更快,但内存消耗更大。例如:

select /*+ HASH_AGG() */ count(*) from t1, t2 where t1.a > 10 group by t1.id;

STREAM_AGG()

STREAM_AGG() Hint 告诉优化器在指定查询块的所有聚合函数中使用流式聚合算法。通常,该算法内存消耗较少,但处理时间较长。如果数据量很大或系统内存不足,建议使用该 Hint。例如:

select /*+ STREAM_AGG() */ count(*) from t1, t2 where t1.a > 10 group by t1.id;

MPP_1PHASE_AGG()

MPP_1PHASE_AGG() 告诉优化器在指定查询块的所有聚合函数中使用单阶段聚合算法。该 Hint 仅在 MPP 模式下生效。例如:

SELECT /*+ MPP_1PHASE_AGG() */ COUNT(*) FROM t1, t2 WHERE t1.a > 10 GROUP BY t1.id;

MPP_2PHASE_AGG()

MPP_2PHASE_AGG() 告诉优化器在指定查询块的所有聚合函数中使用两阶段聚合算法。该 Hint 仅在 MPP 模式下生效。例如:

SELECT /*+ MPP_2PHASE_AGG() */ COUNT(*) FROM t1, t2 WHERE t1.a > 10 GROUP BY t1.id;

USE_INDEX(t1_name, idx1_name [, idx2_name ...])

USE_INDEX(t1_name, idx1_name [, idx2_name ...]) Hint 告诉优化器仅对指定的 t1_name 表使用给定的索引。例如,应用以下 Hint 的效果等同于执行 select * from t t1 use index(idx1, idx2); 语句。

SELECT /*+ USE_INDEX(t1, idx1, idx2) */ * FROM t1;

FORCE_INDEX(t1_name, idx1_name [, idx2_name ...])

FORCE_INDEX(t1_name, idx1_name [, idx2_name ...]) Hint 告诉优化器仅使用给定的索引。

FORCE_INDEX(t1_name, idx1_name [, idx2_name ...]) 的用法和效果与 USE_INDEX(t1_name, idx1_name [, idx2_name ...]) 完全相同。

以下 4 个查询效果相同:

SELECT /*+ USE_INDEX(t, idx1) */ * FROM t; SELECT /*+ FORCE_INDEX(t, idx1) */ * FROM t; SELECT * FROM t use index(idx1); SELECT * FROM t force index(idx1);

IGNORE_INDEX(t1_name, idx1_name [, idx2_name ...])

IGNORE_INDEX(t1_name, idx1_name [, idx2_name ...]) Hint 告诉优化器对指定的 t1_name 表忽略给定的索引。例如,应用以下 Hint 的效果等同于执行 select * from t t1 ignore index(idx1, idx2); 语句。

select /*+ IGNORE_INDEX(t1, idx1, idx2) */ * from t t1;

ORDER_INDEX(t1_name, idx1_name [, idx2_name ...])

ORDER_INDEX(t1_name, idx1_name [, idx2_name ...]) Hint 告诉优化器仅对指定表使用给定索引,并按顺序读取指定索引。

该 Hint 通常用于如下场景:

CREATE TABLE t(a INT, b INT, key(a), key(b)); EXPLAIN SELECT /*+ ORDER_INDEX(t, a) */ a FROM t ORDER BY a LIMIT 10;
+----------------------------+---------+-----------+---------------------+-------------------------------+ | id | estRows | task | access object | operator info | +----------------------------+---------+-----------+---------------------+-------------------------------+ | Limit_10 | 10.00 | root | | offset:0, count:10 | | └─IndexReader_14 | 10.00 | root | | index:Limit_13 | | └─Limit_13 | 10.00 | cop[tikv] | | offset:0, count:10 | | └─IndexFullScan_12 | 10.00 | cop[tikv] | table:t, index:a(a) | keep order:true, stats:pseudo | +----------------------------+---------+-----------+---------------------+-------------------------------+

优化器会为该查询生成两种计划:Limit + IndexScan(keep order: true)TopN + IndexScan(keep order: false)。使用 ORDER_INDEX Hint 时,优化器会选择第一种按顺序读取索引的计划。

NO_ORDER_INDEX(t1_name, idx1_name [, idx2_name ...])

NO_ORDER_INDEX(t1_name, idx1_name [, idx2_name ...]) Hint 告诉优化器仅对指定表使用给定索引,并且不按顺序读取指定索引。该 Hint 通常用于如下场景。

以下示例表明该查询语句的效果等同于 SELECT * FROM t t1 use index(idx1, idx2);

CREATE TABLE t(a INT, b INT, key(a), key(b)); EXPLAIN SELECT /*+ NO_ORDER_INDEX(t, a) */ a FROM t ORDER BY a LIMIT 10;
+----------------------------+----------+-----------+---------------------+--------------------------------+ | id | estRows | task | access object | operator info | +----------------------------+----------+-----------+---------------------+--------------------------------+ | TopN_7 | 10.00 | root | | test.t.a, offset:0, count:10 | | └─IndexReader_14 | 10.00 | root | | index:TopN_13 | | └─TopN_13 | 10.00 | cop[tikv] | | test.t.a, offset:0, count:10 | | └─IndexFullScan_12 | 10000.00 | cop[tikv] | table:t, index:a(a) | keep order:false, stats:pseudo | +----------------------------+----------+-----------+---------------------+--------------------------------+

ORDER_INDEX Hint 示例类似,优化器会为该查询生成两种计划:Limit + IndexScan(keep order: true)TopN + IndexScan(keep order: false)。使用 NO_ORDER_INDEX Hint 时,优化器会选择后一种无序读取索引的计划。

AGG_TO_COP()

AGG_TO_COP() Hint 告诉优化器将指定查询块中的聚合操作下推到 coprocessor。如果优化器未下推某些适合下推的聚合函数,建议使用该 Hint。例如:

select /*+ AGG_TO_COP() */ sum(t1.a) from t t1;

LIMIT_TO_COP()

LIMIT_TO_COP() Hint 告诉优化器将指定查询块中的 LimitTopN 算子下推到 coprocessor。如果优化器未进行此类下推操作,建议使用该 Hint。例如:

SELECT /*+ LIMIT_TO_COP() */ * FROM t WHERE a = 1 AND b > 10 ORDER BY c LIMIT 1;

READ_FROM_STORAGE(TIFLASH[t1_name [, tl_name ...]], TIKV[t2_name [, tl_name ...]])

READ_FROM_STORAGE(TIFLASH[t1_name [, tl_name ...]], TIKV[t2_name [, tl_name ...]]) Hint 告诉优化器从指定存储引擎读取指定表。目前该 Hint 支持两个存储引擎参数:TIKVTIFLASH。如果表有别名,使用别名作为 READ_FROM_STORAGE() 的参数;如果没有别名,则使用表的原始名称。例如:

select /*+ READ_FROM_STORAGE(TIFLASH[t1], TIKV[t2]) */ t1.a from t t1, t t2 where t1.a = t2.a;

USE_INDEX_MERGE(t1_name, idx1_name [, idx2_name ...])

USE_INDEX_MERGE(t1_name, idx1_name [, idx2_name ...]) Hint 告诉优化器对指定表使用索引合并访问方式。索引合并分为交集型和并集型两种。详情参见 Explain 语句中的索引合并

如果你显式指定了索引列表,TiDB 会从该列表中选择索引构建索引合并;如果未指定索引列表,TiDB 会从所有可用索引中选择。

对于交集型索引合并,Hint 中的索引列表为必选参数;对于并集型索引合并,索引列表为可选参数。示例如下:

SELECT /*+ USE_INDEX_MERGE(t1, idx_a, idx_b, idx_c) */ * FROM t1 WHERE t1.a > 10 OR t1.b > 10;

当对同一张表指定多个 USE_INDEX_MERGE Hint 时,优化器会尝试从这些 Hint 指定的索引集合的并集中选择索引。

LEADING(t1_name [, tl_name ...])

LEADING(t1_name [, tl_name ...]) Hint 提示优化器在生成执行计划时,按照 Hint 中指定的表名顺序确定多表连接的顺序。例如:

SELECT /*+ LEADING(t1, t2) */ * FROM t1, t2, t3 WHERE t1.id = t2.id and t2.id = t3.id;

在上述多表连接查询中,连接顺序由 LEADING() Hint 指定的表名顺序决定。优化器会先连接 t1t2,再将结果与 t3 连接。该 Hint 比 STRAIGHT_JOIN 更通用。

LEADING Hint 在以下情况下不会生效:

  • 指定了多个 LEADING Hint。
  • LEADING Hint 中指定的表名不存在。
  • LEADING Hint 中指定了重复的表名。
  • 优化器无法按照 LEADING Hint 指定的顺序进行连接操作。
  • 已存在 straight_join() Hint。
  • 查询中包含外连接且存在笛卡尔积。

上述情况下会生成警告。

-- 指定了多个 `LEADING` Hint。 SELECT /*+ LEADING(t1, t2) LEADING(t3) */ * FROM t1, t2, t3 WHERE t1.id = t2.id and t2.id = t3.id; -- 查看 `LEADING` Hint 失效原因,可执行 `show warnings`。 SHOW WARNINGS;
+---------+------+-------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +---------+------+-------------------------------------------------------------------------------------------------------------------+ | Warning | 1815 | We can only use one leading hint at most, when multiple leading hints are used, all leading hints will be invalid | +---------+------+-------------------------------------------------------------------------------------------------------------------+

MERGE()

在包含公共表表达式(CTE)的查询中使用 MERGE() Hint,可以禁用子查询的物化,将子查询内联展开为 CTE。该 Hint 仅适用于非递归 CTE。在某些场景下,使用 MERGE() 比默认的分配临时空间有更高的执行效率,例如谓词下推或嵌套 CTE 查询:

-- 使用 Hint 将外层查询的谓词下推。 WITH CTE AS (SELECT /*+ MERGE() */ * FROM tc WHERE tc.a < 60) SELECT * FROM CTE WHERE CTE.a < 18; -- 在嵌套 CTE 查询中使用 Hint,将 CTE 内联展开到外层查询。 WITH CTE1 AS (SELECT * FROM t1), CTE2 AS (WITH CTE3 AS (SELECT /*+ MERGE() */ * FROM t2), CTE4 AS (SELECT * FROM t3) SELECT * FROM CTE3, CTE4) SELECT * FROM CTE1, CTE2;

全局生效的 Hint

全局 Hint 在 视图 中生效。当指定为全局 Hint 时,查询中定义的 Hint 可以在视图内部生效。要指定全局 Hint,首先使用 QB_NAME Hint 定义查询块名称,然后以 ViewName@QueryBlockName 的形式添加目标 Hint。

步骤 1:使用 QB_NAME Hint 定义视图的查询块名称

使用 QB_NAME Hint 为视图的每个查询块定义新名称。视图的 QB_NAME Hint 定义方式与查询块一致,但语法从 QB_NAME(QB) 扩展为 QB_NAME(QB, ViewName@QueryBlockName [.ViewName@QueryBlockName .ViewName@QueryBlockName ...])

  • 对于只有单个视图且无子查询的简单语句,以下示例指定了视图 v 的第一个查询块名称:

    SELECT /* Comment: The name of the current query block is the default @SEL_1 */ * FROM v;

    对于视图 v,从查询语句开始,列表中的第一个视图名称为 v@SEL_1。视图 v 的第一个查询块可以声明为 QB_NAME(v_1, v@SEL_1 .@SEL_1),也可以简写为 QB_NAME(v_1, v),省略 @SEL_1

    CREATE VIEW v AS SELECT /* Comment: The name of the current query block is the default @SEL_1 */ * FROM t; -- 指定全局 Hint SELECT /*+ QB_NAME(v_1, v) USE_INDEX(t@v_1, idx) */ * FROM v;
  • 对于包含嵌套视图和子查询的复杂语句,以下示例为视图 v1v2 的每个查询块指定了名称:

    SELECT /* Comment: The name of the current query block is the default @SEL_1 */ * FROM v2 JOIN ( SELECT /* Comment: The name of the current query block is the default @SEL_2 */ * FROM v2) vv;

    对于第一个视图 v2,从第一个查询语句开始,列表中的第一个视图名称为 v2@SEL_1。对于第二个视图 v2,第一个视图名称为 v2@SEL_2。以下示例只考虑第一个视图 v2

    视图 v2 的第一个查询块可以声明为 QB_NAME(v2_1, v2@SEL_1 .@SEL_1),第二个查询块可以声明为 QB_NAME(v2_2, v2@SEL_1 .@SEL_2)

    CREATE VIEW v2 AS SELECT * FROM t JOIN /* Comment: For view v2, the name of the current query block is the default @SEL_1. So, the current query block view list is v2@SEL_1 .@SEL_1 */ ( SELECT COUNT(*) FROM t1 JOIN v1 /* Comment: For view v2, the name of the current query block is the default @SEL_2. So, the current query block view list is v2@SEL_1 .@SEL_2 */ ) tt;

    对于视图 v1,从上述语句开始,列表中的第一个视图名称为 v2@SEL_1 .v1@SEL_2。视图 v1 的第一个查询块可以声明为 QB_NAME(v1_1, v2@SEL_1 .v1@SEL_2 .@SEL_1),第二个查询块可以声明为 QB_NAME(v1_2, v2@SEL_1 .v1@SEL_2 .@SEL_2)

    CREATE VIEW v1 AS SELECT * FROM t JOIN /* Comment: For view `v1`, the name of the current query block is the default @SEL_1. So, the current query block view list is v2@SEL_1 .@SEL_2 .v1@SEL_1 */ ( SELECT COUNT(*) FROM t1 JOIN t2 /* Comment: For view `v1`, the name of the current query block is the default @SEL_2. So, the current query block view list is v2@SEL_1 .@SEL_2 .v1@SEL_2 */ ) tt;

步骤 2:添加目标 Hint

在为视图的查询块定义好 QB_NAME Hint 后,可以以 ViewName@QueryBlockName 的形式添加所需的查询块范围内生效的 Hint,使其在视图内部生效。例如:

  • 为视图 v2 的第一个查询块指定 MERGE_JOIN() Hint:

    SELECT /*+ QB_NAME(v2_1, v2) merge_join(t@v2_1) */ * FROM v2;
  • 为视图 v2 的第二个查询块指定 MERGE_JOIN()STREAM_AGG() Hint:

    SELECT /*+ QB_NAME(v2_2, v2.@SEL_2) merge_join(t1@v2_2) stream_agg(@v2_2) */ * FROM v2;
  • 为视图 v1 的第一个查询块指定 HASH_JOIN() Hint:

    SELECT /*+ QB_NAME(v1_1, v2.v1@SEL_2) hash_join(t@v1_1) */ * FROM v2;
  • 为视图 v1 的第二个查询块指定 HASH_JOIN()HASH_AGG() Hint:

    SELECT /*+ QB_NAME(v1_2, v2.v1@SEL_2 .@SEL_2) hash_join(t1@v1_2) hash_agg(@v1_2) */ * FROM v2;

整个查询范围内生效的 Hint

此类 Hint 只能跟在第一个 SELECTUPDATEDELETE 关键字后面,相当于在执行该查询时修改指定系统变量的值。Hint 的优先级高于已有的系统变量。

NO_INDEX_MERGE()

NO_INDEX_MERGE() Hint 禁用优化器的索引合并功能。

例如,以下查询不会使用索引合并:

select /*+ NO_INDEX_MERGE() */ * from t where t.a > 0 or t.b > 0;

除了该 Hint,还可以通过设置系统变量 tidb_enable_index_merge 控制是否启用该功能。

USE_TOJA(boolean_value)

boolean_value 参数可以为 TRUEFALSEUSE_TOJA(TRUE) Hint 启用优化器将包含子查询的 in 条件转换为 join 和聚合操作。相对地,USE_TOJA(FALSE) Hint 禁用该功能。

例如,以下查询会将 in (select t2.a from t2) subq 转换为对应的 join 和聚合操作:

select /*+ USE_TOJA(TRUE) */ t1.a, t1.b from t1 where t1.a in (select t2.a from t2) subq;

除了该 Hint,还可以通过设置系统变量 tidb_opt_insubq_to_join_and_agg 控制是否启用该功能。

MAX_EXECUTION_TIME(N)

MAX_EXECUTION_TIME(N) Hint 为语句设置一个最大执行时长 N(单位为毫秒),超时后服务器会终止该语句。以下 Hint 中,MAX_EXECUTION_TIME(1000) 表示超时时间为 1000 毫秒(即 1 秒):

select /*+ MAX_EXECUTION_TIME(1000) */ * from t1 inner join t2 where t1.id = t2.id;

除了该 Hint,还可以通过系统变量 global.max_execution_time 限制语句的执行时间。

MEMORY_QUOTA(N)

MEMORY_QUOTA(N) Hint 为语句设置一个内存使用上限 N(单位为 MB 或 GB)。当语句内存使用超过该限制时,TiDB 会根据超限行为记录日志或直接终止语句。

以下 Hint 中,MEMORY_QUOTA(1024 MB) 表示内存使用限制为 1024 MB:

select /*+ MEMORY_QUOTA(1024 MB) */ * from t;

除了该 Hint,还可以通过 tidb_mem_quota_query 系统变量限制语句的内存使用。

READ_CONSISTENT_REPLICA()

READ_CONSISTENT_REPLICA() Hint 启用从 TiKV follower 节点读取一致性数据的功能。例如:

select /*+ READ_CONSISTENT_REPLICA() */ * from t;

除了该 Hint,还可以通过设置环境变量 tidb_replica_read'follower''leader' 控制是否启用该功能。

IGNORE_PLAN_CACHE()

IGNORE_PLAN_CACHE() Hint 提示优化器在处理当前 prepare 语句时不使用 Plan Cache。

该 Hint 用于在 prepare-plan-cache 启用时,临时禁用某类查询的 Plan Cache。

以下示例在执行 prepare 语句时强制禁用 Plan Cache。

prepare stmt from 'select /*+ IGNORE_PLAN_CACHE() */ * from t where t.id = ?';

SET_VAR(VAR_NAME=VAR_VALUE)

你可以通过 SET_VAR(VAR_NAME=VAR_VALUE) Hint 在语句执行期间临时修改系统变量的值。语句执行结束后,当前会话中的系统变量值会自动恢复为原值。该 Hint 可用于修改部分与优化器和执行器相关的系统变量。可通过 系统变量 查看支持通过该 Hint 修改的变量列表。

示例如下:

SELECT /*+ SET_VAR(MAX_EXECUTION_TIME=1234) */ @@MAX_EXECUTION_TIME; SELECT @@MAX_EXECUTION_TIME;

执行上述 SQL 后,第一条查询返回 Hint 中设置的值 1234,而不是 MAX_EXECUTION_TIME 的默认值。第二条查询返回变量的默认值。

+----------------------+ | @@MAX_EXECUTION_TIME | +----------------------+ | 1234 | +----------------------+ 1 row in set (0.00 sec) +----------------------+ | @@MAX_EXECUTION_TIME | +----------------------+ | 0 | +----------------------+ 1 row in set (0.00 sec)

STRAIGHT_JOIN()

STRAIGHT_JOIN() Hint 提示优化器在生成连接计划时,按照 FROM 子句中表名的顺序进行连接。

SELECT /*+ STRAIGHT_JOIN() */ * FROM t t1, t t2 WHERE t1.a = t2.a;

NTH_PLAN(N)

NTH_PLAN(N) Hint 提示优化器选择物理优化过程中找到的第 N 个物理计划。N 必须为正整数。

如果指定的 N 超出物理优化的搜索范围,TiDB 会返回警告,并在忽略该 Hint 的情况下选择最优物理计划。

当启用 cascades planner 时,该 Hint 不生效。

以下示例强制优化器选择物理优化过程中找到的第三个物理计划:

SELECT /*+ NTH_PLAN(3) */ count(*) from t where a > 5;

RESOURCE_GROUP(resource_group_name)

RESOURCE_GROUP(resource_group_name) 用于资源管控实现资源隔离。该 Hint 临时将当前语句在指定资源组下执行。如果指定的资源组不存在,则该 Hint 会被忽略。

示例:

SELECT /*+ RESOURCE_GROUP(rg1) */ * FROM t limit 10;

排查 Hint 不生效的常见问题

Hint 不生效是因为 MySQL 命令行客户端会去除 Hint

5.7.7 之前的 MySQL 命令行客户端默认会去除优化器 Hint。如果你想在这些早期版本中使用 Hint 语法,启动客户端时需加上 --comments 选项。例如:mysql -h 127.0.0.1 -P 4000 -uroot --comments

Hint 不生效是因为未指定数据库名

如果连接时未指定数据库名,Hint 可能不会生效。例如:

连接 TiDB 时,使用 mysql -h127.0.0.1 -P4000 -uroot 命令(未加 -D 选项),然后执行以下 SQL:

SELECT /*+ use_index(t, a) */ a FROM test.t; SHOW WARNINGS;

由于 TiDB 无法识别表 t 所属的数据库,use_index(t, a) Hint 不生效。

+---------+------+----------------------------------------------------------------------+ | Level | Code | Message | +---------+------+----------------------------------------------------------------------+ | Warning | 1815 | use_index(.t, a) is inapplicable, check whether the table(.t) exists | +---------+------+----------------------------------------------------------------------+ 1 row in set (0.00 sec)

Hint 不生效是因为跨库查询未显式指定数据库名

执行跨库查询时,需要显式指定数据库名,否则 Hint 可能不会生效。例如:

USE test1; CREATE TABLE t1(a INT, KEY(a)); USE test2; CREATE TABLE t2(a INT, KEY(a)); SELECT /*+ use_index(t1, a) */ * FROM test1.t1, t2; SHOW WARNINGS;

上述语句中,表 t1 不在当前 test2 数据库中,因此 use_index(t1, a) Hint 不生效。

+---------+------+----------------------------------------------------------------------------------+ | Level | Code | Message | +---------+------+----------------------------------------------------------------------------------+ | Warning | 1815 | use_index(test2.t1, a) is inapplicable, check whether the table(test2.t1) exists | +---------+------+----------------------------------------------------------------------------------+ 1 row in set (0.00 sec)

此时需要将 Hint 写为 use_index(test1.t1, a),而不是 use_index(t1, a)

Hint 不生效是因为位置不正确

Hint 不是紧跟在指定关键字后面时不会生效。例如:

SELECT * /*+ use_index(t, a) */ FROM t; SHOW WARNINGS;

警告如下:

+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Warning | 1064 | You have an error in your SQL syntax; check the manual that corresponds to your TiDB version for the right syntax to use [parser:8066]Optimizer hint can only be followed by certain keywords like SELECT, INSERT, etc. | +---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.01 sec)

此时需要将 Hint 紧跟在 SELECT 关键字后面。详情参见语法部分。

INL_JOIN Hint 不生效

使用内置函数对连接列进行操作时 INL_JOIN Hint 不生效

在某些情况下,如果你对连接表的列使用了内置函数,优化器可能无法选择 IndexJoin 计划,导致 INL_JOIN Hint 也不生效。

例如,以下查询在连接列 tname 上使用了内置函数 substr

CREATE TABLE t1 (id varchar(10) primary key, tname varchar(10)); CREATE TABLE t2 (id varchar(10) primary key, tname varchar(10)); EXPLAIN SELECT /*+ INL_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id=t2.id and SUBSTR(t1.tname,1,2)=SUBSTR(t2.tname,1,2);

执行计划如下:

+------------------------------+----------+-----------+---------------+-----------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +------------------------------+----------+-----------+---------------+-----------------------------------------------------------------------+ | HashJoin_12 | 12500.00 | root | | inner join, equal:[eq(test.t1.id, test.t2.id) eq(Column#5, Column#6)] | | ├─Projection_17(Build) | 10000.00 | root | | test.t2.id, test.t2.tname, substr(test.t2.tname, 1, 2)->Column#6 | | │ └─TableReader_19 | 10000.00 | root | | data:TableFullScan_18 | | │ └─TableFullScan_18 | 10000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo | | └─Projection_14(Probe) | 10000.00 | root | | test.t1.id, test.t1.tname, substr(test.t1.tname, 1, 2)->Column#5 | | └─TableReader_16 | 10000.00 | root | | data:TableFullScan_15 | | └─TableFullScan_15 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | +------------------------------+----------+-----------+---------------+-----------------------------------------------------------------------+ 7 rows in set, 1 warning (0.01 sec)
SHOW WARNINGS;
+---------+------+------------------------------------------------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------------------------------------------------+ | Warning | 1815 | Optimizer Hint /*+ INL_JOIN(t1, t2) */ or /*+ TIDB_INLJ(t1, t2) */ is inapplicable | +---------+------+------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)

如上例所示,INL_JOIN Hint 未生效。这是由于优化器限制,不能将 ProjectionSelection 算子作为 IndexJoin 的 probe 端。

从 TiDB v8.0.0 开始,可以通过将 tidb_enable_inl_join_inner_multi_pattern 设置为 ON 避免该问题。

SET @@tidb_enable_inl_join_inner_multi_pattern=ON; Query OK, 0 rows affected (0.00 sec) EXPLAIN SELECT /*+ INL_JOIN(t1, t2) */ * FROM t1, t2 WHERE t1.id=t2.id AND SUBSTR(t1.tname,1,2)=SUBSTR(t2.tname,1,2); +------------------------------+--------------+-----------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +------------------------------+--------------+-----------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | IndexJoin_18 | 12500.00 | root | | inner join, inner:Projection_14, outer key:test.t1.id, inner key:test.t2.id, equal cond:eq(Column#5, Column#6), eq(test.t1.id, test.t2.id) | | ├─Projection_32(Build) | 10000.00 | root | | test.t1.id, test.t1.tname, substr(test.t1.tname, 1, 2)->Column#5 | | │ └─TableReader_34 | 10000.00 | root | | data:TableFullScan_33 | | │ └─TableFullScan_33 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | | └─Projection_14(Probe) | 100000000.00 | root | | test.t2.id, test.t2.tname, substr(test.t2.tname, 1, 2)->Column#6 | | └─TableReader_13 | 10000.00 | root | | data:TableRangeScan_12 | | └─TableRangeScan_12 | 10000.00 | cop[tikv] | table:t2 | range: decided by [eq(test.t2.id, test.t1.id)], keep order:false, stats:pseudo | +------------------------------+--------------+-----------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------+ 7 rows in set (0.00 sec)

INL_JOININL_HASH_JOININL_MERGE_JOIN Hint 因排序规则不兼容不生效

当连接键的排序规则在两张表之间不兼容时,无法使用 IndexJoin 算子执行查询,此时 INL_JOININL_HASH_JOININL_MERGE_JOIN Hint 都不会生效。例如:

CREATE TABLE t1 (k varchar(8), key(k)) COLLATE=utf8mb4_general_ci; CREATE TABLE t2 (k varchar(8), key(k)) COLLATE=utf8mb4_bin; EXPLAIN SELECT /*+ tidb_inlj(t1) */ * FROM t1, t2 WHERE t1.k=t2.k;

执行计划如下:

+-----------------------------+----------+-----------+----------------------+----------------------------------------------+ | id | estRows | task | access object | operator info | +-----------------------------+----------+-----------+----------------------+----------------------------------------------+ | HashJoin_19 | 12487.50 | root | | inner join, equal:[eq(test.t1.k, test.t2.k)] | | ├─IndexReader_24(Build) | 9990.00 | root | | index:IndexFullScan_23 | | │ └─IndexFullScan_23 | 9990.00 | cop[tikv] | table:t2, index:k(k) | keep order:false, stats:pseudo | | └─IndexReader_22(Probe) | 9990.00 | root | | index:IndexFullScan_21 | | └─IndexFullScan_21 | 9990.00 | cop[tikv] | table:t1, index:k(k) | keep order:false, stats:pseudo | +-----------------------------+----------+-----------+----------------------+----------------------------------------------+ 5 rows in set, 1 warning (0.00 sec)

上述语句中,t1.kt2.k 的排序规则分别为 utf8mb4_general_ciutf8mb4_bin,不兼容,导致 INL_JOINTIDB_INLJ Hint 不生效。

SHOW WARNINGS; +---------+------+----------------------------------------------------------------------------+ | Level | Code | Message | +---------+------+----------------------------------------------------------------------------+ | Warning | 1815 | Optimizer Hint /*+ INL_JOIN(t1) */ or /*+ TIDB_INLJ(t1) */ is inapplicable | +---------+------+----------------------------------------------------------------------------+ 1 row in set (0.00 sec)

INL_JOIN Hint 因连接顺序不生效

INL_JOIN(t1, t2)TIDB_INLJ(t1, t2) Hint 的语义是让 t1t2 作为 IndexJoin 算子的内表与其他表连接,而不是直接对它们进行 IndexJoin。例如:

EXPLAIN SELECT /*+ inl_join(t1, t3) */ * FROM t1, t2, t3 WHERE t1.id = t2.id AND t2.id = t3.id AND t1.id = t3.id; +---------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +---------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | IndexJoin_16 | 15625.00 | root | | inner join, inner:TableReader_13, outer key:test.t2.id, test.t1.id, inner key:test.t3.id, test.t3.id, equal cond:eq(test.t1.id, test.t3.id), eq(test.t2.id, test.t3.id) | | ├─IndexJoin_34(Build) | 12500.00 | root | | inner join, inner:TableReader_31, outer key:test.t2.id, inner key:test.t1.id, equal cond:eq(test.t2.id, test.t1.id) | | │ ├─TableReader_40(Build) | 10000.00 | root | | data:TableFullScan_39 | | │ │ └─TableFullScan_39 | 10000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo | | │ └─TableReader_31(Probe) | 10000.00 | root | | data:TableRangeScan_30 | | │ └─TableRangeScan_30 | 10000.00 | cop[tikv] | table:t1 | range: decided by [test.t2.id], keep order:false, stats:pseudo | | └─TableReader_13(Probe) | 12500.00 | root | | data:TableRangeScan_12 | | └─TableRangeScan_12 | 12500.00 | cop[tikv] | table:t3 | range: decided by [test.t2.id test.t1.id], keep order:false, stats:pseudo | +---------------------------------+----------+-----------+---------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

如上例所示,t1t3 并未直接通过 IndexJoin 连接。

如果要让 t1t3 直接进行 IndexJoin,可以先使用 LEADING(t1, t3) Hint 指定连接顺序,再用 INL_JOIN Hint 指定连接算法。例如:

EXPLAIN SELECT /*+ leading(t1, t3), inl_join(t3) */ * FROM t1, t2, t3 WHERE t1.id = t2.id AND t2.id = t3.id AND t1.id = t3.id; +---------------------------------+----------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------+ | id | estRows | task | access object | operator info | +---------------------------------+----------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------+ | Projection_12 | 15625.00 | root | | test.t1.id, test.t1.name, test.t2.id, test.t2.name, test.t3.id, test.t3.name | | └─HashJoin_21 | 15625.00 | root | | inner join, equal:[eq(test.t1.id, test.t2.id) eq(test.t3.id, test.t2.id)] | | ├─TableReader_36(Build) | 10000.00 | root | | data:TableFullScan_35 | | │ └─TableFullScan_35 | 10000.00 | cop[tikv] | table:t2 | keep order:false, stats:pseudo | | └─IndexJoin_28(Probe) | 12500.00 | root | | inner join, inner:TableReader_25, outer key:test.t1.id, inner key:test.t3.id, equal cond:eq(test.t1.id, test.t3.id) | | ├─TableReader_34(Build) | 10000.00 | root | | data:TableFullScan_33 | | │ └─TableFullScan_33 | 10000.00 | cop[tikv] | table:t1 | keep order:false, stats:pseudo | | └─TableReader_25(Probe) | 10000.00 | root | | data:TableRangeScan_24 | | └─TableRangeScan_24 | 10000.00 | cop[tikv] | table:t3 | range: decided by [test.t1.id], keep order:false, stats:pseudo | +---------------------------------+----------+-----------+---------------+---------------------------------------------------------------------------------------------------------------------+ 9 rows in set (0.01 sec)

使用 Hint 导致 Can't find a proper physical plan for this query 错误

在以下场景中,可能会出现 Can't find a proper physical plan for this query 错误:

  • 查询本身不需要按顺序读取索引。即对于该查询,优化器无论如何都不会生成按顺序读取索引的计划。如果指定了 ORDER_INDEX Hint,则会报此错误。此时应移除对应的 ORDER_INDEX Hint。
  • 通过 NO_JOIN 相关 Hint 排除了所有可能的连接方式。
CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT); EXPLAIN SELECT /*+ NO_HASH_JOIN(t1), NO_MERGE_JOIN(t1) */ * FROM t1, t2 WHERE t1.a=t2.a; ERROR 1815 (HY000): Internal : Can't find a proper physical plan for this query
CREATE TABLE t1 (a INT); CREATE TABLE t2 (a INT); set tidb_opt_enable_hash_join=off; EXPLAIN SELECT /*+ NO_MERGE_JOIN(t1) */ * FROM t1, t2 WHERE t1.a=t2.a; ERROR 1815 (HY000): Internal : Can't find a proper physical plan for this query

SET_VAR 写在子查询中不生效

SET_VAR 用于修改当前语句的系统变量值。不要将其写在子查询中,否则由于子查询的特殊处理,SET_VAR 可能不会生效。

以下示例中,SET_VAR 写在子查询中,因此不生效。

mysql> SELECT @@MAX_EXECUTION_TIME, a FROM (SELECT /*+ SET_VAR(MAX_EXECUTION_TIME=123) */ 1 as a) t; +----------------------+---+ | @@MAX_EXECUTION_TIME | a | +----------------------+---+ | 0 | 1 | +----------------------+---+ 1 row in set (0.00 sec)

以下示例中,SET_VAR 未写在子查询中,因此生效。

mysql> SELECT /*+ SET_VAR(MAX_EXECUTION_TIME=123) */ @@MAX_EXECUTION_TIME, a FROM (SELECT 1 as a) t; +----------------------+---+ | @@MAX_EXECUTION_TIME | a | +----------------------+---+ | 123 | 1 | +----------------------+---+ 1 row in set (0.00 sec)

文档内容是否有帮助?