文档主页
MySQL 8.4 参考手册
相关文档 下载本手册
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 参考手册  /  ...  /  优化器提示

10.9.3 优化器提示

控制优化器策略的一种方法是设置 optimizer_switch 系统变量(参见 第 10.9.2 节,“可切换优化”)。对该变量的更改会影响所有后续查询的执行;要以与其他查询不同的方式影响一个查询,则需要在每个查询之前更改 optimizer_switch

控制优化器的另一种方法是使用优化器提示,可以在单个语句中指定。由于优化器提示在每个语句的基础上应用,因此它们提供了比使用 optimizer_switch 可以实现的更细粒度的语句执行计划控制。例如,您可以为语句中的一个表启用优化,而为另一个表禁用优化。语句中的提示优先于 optimizer_switch 标志。

示例

SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1
  FROM t3 WHERE f1 > 30 AND f1 < 33;
SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2 WHERE ...;
SELECT /*+ NO_ICP(t1, t2) */ * FROM t1 INNER JOIN t2 WHERE ...;
SELECT /*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) */ * FROM t1 ...;
EXPLAIN SELECT /*+ NO_ICP(t1) */ * FROM t1 WHERE ...;
SELECT /*+ MERGE(dt) */ * FROM (SELECT * FROM t1) AS dt;
INSERT /*+ SET_VAR(foreign_key_checks=OFF) */ INTO t2 VALUES(2);

此处描述的优化器提示不同于 第 10.9.4 节,“索引提示” 中描述的索引提示。优化器提示和索引提示可以单独使用或一起使用。

优化器提示概述

优化器提示在不同的范围级别应用

  • 全局:提示影响整个语句

  • 查询块:提示影响语句中的特定查询块

  • 表级:提示影响查询块中的特定表

  • 索引级:提示影响表中的特定索引

下表总结了可用的优化器提示、它们影响的优化器策略以及它们应用的范围或范围。更多详细信息将在后面给出。

表 10.2 可用的优化器提示

提示名称 描述 适用范围
BKANO_BKA 影响批处理键访问连接处理 查询块,表
BNLNO_BNL 影响哈希连接优化 查询块,表
DERIVED_CONDITION_PUSHDOWNNO_DERIVED_CONDITION_PUSHDOWN 对物化派生表使用或忽略派生条件下推优化 查询块,表
GROUP_INDEXNO_GROUP_INDEX GROUP BY 操作中使用或忽略指定的索引或索引进行索引扫描 索引
HASH_JOINNO_HASH_JOIN 影响哈希连接优化(在 MySQL 8.4 中没有影响) 查询块,表
INDEXNO_INDEX 作为 JOIN_INDEXGROUP_INDEXORDER_INDEX 的组合,或作为 NO_JOIN_INDEXNO_GROUP_INDEXNO_ORDER_INDEX 的组合 索引
INDEX_MERGENO_INDEX_MERGE 影响索引合并优化 表、索引
JOIN_FIXED_ORDER 使用 FROM 子句中指定的表顺序进行联接顺序 查询块
JOIN_INDEXNO_JOIN_INDEX 使用或忽略指定的索引或索引用于任何访问方法 索引
JOIN_ORDER 使用提示中指定的表顺序进行联接顺序 查询块
JOIN_PREFIX 使用提示中指定的表顺序进行联接顺序的第一个表 查询块
JOIN_SUFFIX 使用提示中指定的表顺序进行联接顺序的最后一个表 查询块
MAX_EXECUTION_TIME 限制语句执行时间 全局
MERGENO_MERGE 影响派生表/视图合并到外部查询块中
MRRNO_MRR 影响多范围读取优化 表、索引
NO_ICP 影响索引条件下推优化 表、索引
NO_RANGE_OPTIMIZATION 影响范围优化 表、索引
ORDER_INDEXNO_ORDER_INDEX 使用或忽略指定的索引或索引用于排序行 索引
QB_NAME 为查询块分配名称 查询块
RESOURCE_GROUP 在语句执行期间设置资源组 全局
SEMIJOINNO_SEMIJOIN 影响半联接和反联接策略 查询块
SKIP_SCANNO_SKIP_SCAN 影响跳过扫描优化 表、索引
SET_VAR 在语句执行期间设置变量 全局
SUBQUERY 影响物化、INEXISTS 子查询策略 查询块

禁用优化会阻止优化器使用它。启用优化意味着优化器可以自由使用该策略,如果它适用于语句执行,而不是优化器一定会使用它。

优化器提示语法

MySQL 支持 SQL 语句中的注释,如 第 11.7 节,“注释” 中所述。优化器提示必须在 /*+ ... */ 注释中指定。也就是说,优化器提示使用 /* ... */ C 风格注释语法的变体,在 /* 注释开头序列后加一个 + 字符。示例

/*+ BKA(t1) */
/*+ BNL(t1, t2) */
/*+ NO_RANGE_OPTIMIZATION(t4 PRIMARY) */
/*+ QB_NAME(qb2) */

+ 字符后允许使用空格。

解析器在 SELECTUPDATEINSERTREPLACEDELETE 语句的初始关键字之后识别优化器提示注释。在以下情况下允许使用提示

  • 在查询和数据更改语句的开头

    SELECT /*+ ... */ ...
    INSERT /*+ ... */ ...
    REPLACE /*+ ... */ ...
    UPDATE /*+ ... */ ...
    DELETE /*+ ... */ ...
  • 在查询块的开头

    (SELECT /*+ ... */ ... )
    (SELECT ... ) UNION (SELECT /*+ ... */ ... )
    (SELECT /*+ ... */ ... ) UNION (SELECT /*+ ... */ ... )
    UPDATE ... WHERE x IN (SELECT /*+ ... */ ...)
    INSERT ... SELECT /*+ ... */ ...
  • 在由 EXPLAIN 作为前缀的提示语句中。例如

    EXPLAIN SELECT /*+ ... */ ...
    EXPLAIN UPDATE ... WHERE x IN (SELECT /*+ ... */ ...)

    这意味着可以使用 EXPLAIN 查看优化器提示如何影响执行计划。在 EXPLAIN 之后立即使用 SHOW WARNINGS 查看提示是如何使用的。随后的 SHOW WARNINGS 显示的扩展 EXPLAIN 输出指示使用了哪些提示。忽略的提示不会显示。

提示注释可以包含多个提示,但查询块不能包含多个提示注释。这有效

SELECT /*+ BNL(t1) BKA(t2) */ ...

但这无效

SELECT /*+ BNL(t1) */ /* BKA(t2) */ ...

当提示注释包含多个提示时,可能会出现重复和冲突。以下是一些一般准则。对于特定类型的提示,可能会适用其他规则,如提示描述中所示。

  • 重复提示:对于 /*+ MRR(idx1) MRR(idx1) */ 之类的提示,MySQL 将使用第一个提示,并发出有关重复提示的警告。

  • 冲突提示:对于 /*+ MRR(idx1) NO_MRR(idx1) */ 之类的提示,MySQL 将使用第一个提示,并发出有关第二个冲突提示的警告。

查询块名称是标识符,并遵循有关哪些名称有效以及如何引用它们的常规规则(请参阅 第 11.2 节,“架构对象名称”)。

提示名称、查询块名称和策略名称不区分大小写。对表和索引名称的引用遵循常规的标识符大小写敏感性规则(请参阅 第 11.2.3 节,“标识符大小写敏感性”)。

联接顺序优化器提示

联接顺序提示会影响优化器联接表的顺序。

JOIN_FIXED_ORDER 提示的语法

hint_name([@query_block_name])

其他联接顺序提示的语法

hint_name([@query_block_name] tbl_name [, tbl_name] ...)
hint_name(tbl_name[@query_block_name] [, tbl_name[@query_block_name]] ...)

语法指的是以下术语

  • hint_name: 允许使用以下提示名称

    • JOIN_FIXED_ORDER: 强制优化器使用 FROM 子句中出现的顺序联接表。这与指定 SELECT STRAIGHT_JOIN 相同。

    • JOIN_ORDER: 指示优化器使用指定的表顺序联接表。提示适用于命名的表。优化器可以将未命名的表放在联接顺序中的任何位置,包括指定的表之间。

    • JOIN_PREFIX: 指示优化器使用指定的表顺序进行联接顺序的第一个表。提示适用于命名的表。优化器将所有其他表放在命名表之后。

    • JOIN_SUFFIX: 指示优化器使用指定的表顺序进行联接顺序的最后一个表。提示适用于命名的表。优化器将所有其他表放在命名表之前。

  • tbl_name: 语句中使用的表的名称。命名表的提示适用于它命名的所有表。JOIN_FIXED_ORDER 提示不命名任何表,并适用于它所在的查询块的 FROM 子句中的所有表。

    如果表有别名,则提示必须引用别名,而不是表名。

    提示中的表名不能用模式名限定。

  • query_block_name: 提示适用的查询块。如果提示不包含前导的 @query_block_name,则提示适用于它所在的查询块。对于 tbl_name@query_block_name 语法,提示适用于命名查询块中的命名表。要为查询块分配名称,请参阅 用于命名查询块的优化器提示

示例

SELECT
/*+ JOIN_PREFIX(t2, t5@subq2, t4@subq1)
    JOIN_ORDER(t4@subq1, t3)
    JOIN_SUFFIX(t1) */
COUNT(*) FROM t1 JOIN t2 JOIN t3
           WHERE t1.f1 IN (SELECT /*+ QB_NAME(subq1) */ f1 FROM t4)
             AND t2.f1 IN (SELECT /*+ QB_NAME(subq2) */ f1 FROM t5);

提示控制合并到外部查询块的半联接表的行为。如果子查询 subq1subq2 被转换为半联接,则表 t4@subq1t5@subq2 将被合并到外部查询块。在这种情况下,在外部查询块中指定的提示控制 t4@subq1t5@subq2 表的行为。

优化器根据以下原则解析联接顺序提示

  • 多个提示实例

    仅应用每种类型的 JOIN_PREFIXJOIN_SUFFIX 提示。任何相同类型的后继提示都会被忽略,并发出警告。JOIN_ORDER 可以多次指定。

    示例

    /*+ JOIN_PREFIX(t1) JOIN_PREFIX(t2) */

    第二个 JOIN_PREFIX 提示被忽略,并发出警告。

    /*+ JOIN_PREFIX(t1) JOIN_SUFFIX(t2) */

    这两个提示都适用。不会发出警告。

    /*+ JOIN_ORDER(t1, t2) JOIN_ORDER(t2, t3) */

    这两个提示都适用。不会发出警告。

  • 冲突提示

    在某些情况下,提示可能会冲突,例如当 JOIN_ORDERJOIN_PREFIX 的表顺序无法同时应用时

    SELECT /*+ JOIN_ORDER(t1, t2) JOIN_PREFIX(t2, t1) */ ... FROM t1, t2;

    在这种情况下,将应用第一个指定的提示,随后出现的冲突提示将被忽略,不会发出警告。无法应用的有效提示将被静默忽略,不会发出警告。

  • 忽略的提示

    如果提示中指定的表存在循环依赖关系,则会忽略提示。

    示例

    /*+ JOIN_ORDER(t1, t2) JOIN_PREFIX(t2, t1) */

    JOIN_ORDER 提示将表 t2 设置为依赖于 t1。由于表 t1 不能依赖于 t2,因此 JOIN_PREFIX 提示被忽略。忽略的提示不会显示在扩展的 EXPLAIN 输出中。

  • const 表的交互

    MySQL 优化器将 const 表放在联接顺序的第一个位置,并且 const 表的位置不会受到提示的影响。联接顺序提示中对 const 表的引用会被忽略,尽管提示仍然适用。例如,这些等效

    JOIN_ORDER(t1, const_tbl, t2)
    JOIN_ORDER(t1, t2)

    扩展的 EXPLAIN 输出中显示的已接受的提示包括 const 表,如指定的那样。

  • 与联接操作类型的交互

    MySQL 支持多种类型的联接:LEFTRIGHTINNERCROSSSTRAIGHT_JOIN。与指定的联接类型冲突的提示将被忽略,不会发出警告。

    示例

    SELECT /*+ JOIN_PREFIX(t1, t2) */FROM t2 LEFT JOIN t1;

    这里,提示中请求的联接顺序与 LEFT JOIN 所需的顺序之间存在冲突。提示将被忽略,不会发出警告。

表级优化器提示

表级提示会影响

这些提示类型适用于特定表或查询块中的所有表。

表级提示的语法

hint_name([@query_block_name] [tbl_name [, tbl_name] ...])
hint_name([tbl_name@query_block_name [, tbl_name@query_block_name] ...])

语法指的是以下术语

  • hint_name: 允许使用以下提示名称

    注意

    若要使用块嵌套循环或批处理键访问提示为外联接的任何内部表启用联接缓冲,则必须为外联接的所有内部表启用联接缓冲。

  • tbl_name:语句中使用的表的名称。该提示适用于它命名的所有表。如果该提示没有命名任何表,则它适用于它所在的查询块中的所有表。

    如果表有别名,则提示必须引用别名,而不是表名。

    提示中的表名不能用模式名限定。

  • query_block_name: 提示适用的查询块。如果提示不包含前导的 @query_block_name,则提示适用于它所在的查询块。对于 tbl_name@query_block_name 语法,提示适用于命名查询块中的命名表。要为查询块分配名称,请参阅 用于命名查询块的优化器提示

示例

SELECT /*+ NO_BKA(t1, t2) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3;
SELECT /*+ NO_BNL() BKA(t1) */ t1.* FROM t1 INNER JOIN t2 INNER JOIN t3;
SELECT /*+ NO_MERGE(dt) */ * FROM (SELECT * FROM t1) AS dt;

表级提示适用于从先前表接收记录的表,而不是发送表。请考虑以下语句

SELECT /*+ BNL(t2) */ FROM t1, t2;

如果优化器选择先处理 t1,它会对 t2 应用块嵌套循环联接,在开始从 t2 读取之前缓冲来自 t1 的行。如果优化器改为选择先处理 t2,则该提示没有效果,因为 t2 是一个发送表。

对于 MERGENO_MERGE 提示,以下优先级规则适用

  • 提示优先于任何不是技术约束的优化器启发式算法。(如果提供提示作为建议没有效果,则优化器有理由忽略它。)

  • 提示优先于 derived_mergeoptimizer_switch 系统变量标志。

  • 对于视图引用,视图定义中的 ALGORITHM={MERGE|TEMPTABLE} 子句优先于在引用该视图的查询中指定的提示。

索引级优化器提示

索引级提示会影响优化器对特定表或索引使用哪些索引处理策略。这些提示类型会影响索引条件下推 (ICP)、多范围读取 (MRR)、索引合并和范围优化的使用(参见 第 10.2.1 节,“优化 SELECT 语句”)。

索引级提示的语法

hint_name([@query_block_name] tbl_name [index_name [, index_name] ...])
hint_name(tbl_name@query_block_name [index_name [, index_name] ...])

语法指的是以下术语

  • hint_name: 允许使用以下提示名称

    • GROUP_INDEXNO_GROUP_INDEX:为 GROUP BY 操作的索引扫描启用或禁用指定的索引。

    • INDEXNO_INDEX:充当 JOIN_INDEXGROUP_INDEXORDER_INDEX 的组合,强制服务器对任何和所有范围使用指定的索引,或充当 NO_JOIN_INDEXNO_GROUP_INDEXNO_ORDER_INDEX 的组合,这会导致服务器忽略任何和所有范围内的指定索引。

    • INDEX_MERGENO_INDEX_MERGE:为指定的表或索引启用或禁用索引合并访问方法。有关此访问方法的信息,请参见 第 10.2.1.3 节,“索引合并优化”。这些提示适用于所有三种索引合并算法。

      INDEX_MERGE 提示强制优化器使用索引合并来使用指定的索引集访问指定的表。如果未指定索引,优化器将考虑所有可能的索引组合并选择最便宜的组合。如果索引组合不适用于给定的语句,则可能会忽略该提示。

      NO_INDEX_MERGE 提示会禁用涉及任何指定索引的索引合并组合。如果该提示未指定任何索引,则不允许对该表进行索引合并。

    • JOIN_INDEXNO_JOIN_INDEX:强制 MySQL 对任何访问方法(例如 refrangeindex_merge 等)使用或忽略指定的索引。

    • MRRNO_MRR:为指定的表或索引启用或禁用 MRR。MRR 提示仅适用于 InnoDBMyISAM 表。有关此访问方法的信息,请参见 第 10.2.1.11 节,“多范围读取优化”

    • NO_ICP:为指定的表或索引禁用 ICP。默认情况下,ICP 是一个候选优化策略,因此没有启用它的提示。有关此访问方法的信息,请参见 第 10.2.1.6 节,“索引条件下推优化”

    • NO_RANGE_OPTIMIZATION:为指定的表或索引禁用索引范围访问。此提示还会禁用该表或索引的索引合并和松散索引扫描。默认情况下,范围访问是一个候选优化策略,因此没有启用它的提示。

      当范围数可能很多且范围优化需要大量资源时,此提示可能很有用。

    • ORDER_INDEXNO_ORDER_INDEX:导致 MySQL 对行进行排序时使用或忽略指定的索引。

    • SKIP_SCANNO_SKIP_SCAN:为指定的表或索引启用或禁用跳过扫描访问方法。有关此访问方法的信息,请参见 跳过扫描范围访问方法

      SKIP_SCAN 提示强制优化器使用跳过扫描来使用指定的索引集访问指定的表。如果未指定索引,优化器将考虑所有可能的索引并选择最便宜的索引。如果索引不适用于给定的语句,则可能会忽略该提示。

      NO_SKIP_SCAN 提示会禁用指定索引的跳过扫描。如果该提示未指定任何索引,则不允许对该表进行跳过扫描。

  • tbl_name:提示适用的表。

  • index_name:命名表中索引的名称。该提示适用于它命名的所有索引。如果该提示没有命名任何索引,则它适用于表中的所有索引。

    若要引用主键,请使用名称 PRIMARY。若要查看表的索引名称,请使用 SHOW INDEX

  • query_block_name: 提示适用的查询块。如果提示不包含前导的 @query_block_name,则提示适用于它所在的查询块。对于 tbl_name@query_block_name 语法,提示适用于命名查询块中的命名表。要为查询块分配名称,请参阅 用于命名查询块的优化器提示

示例

SELECT /*+ INDEX_MERGE(t1 f3, PRIMARY) */ f2 FROM t1
  WHERE f1 = 'o' AND f2 = f3 AND f3 <= 4;
SELECT /*+ MRR(t1) */ * FROM t1 WHERE f2 <= 3 AND 3 <= f3;
SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1
  FROM t3 WHERE f1 > 30 AND f1 < 33;
INSERT INTO t3(f1, f2, f3)
  (SELECT /*+ NO_ICP(t2) */ t2.f1, t2.f2, t2.f3 FROM t1,t2
   WHERE t1.f1=t2.f1 AND t2.f2 BETWEEN t1.f1
   AND t1.f2 AND t2.f2 + 1 >= t1.f1 + 1);
SELECT /*+ SKIP_SCAN(t1 PRIMARY) */ f1, f2
  FROM t1 WHERE f2 > 40;

以下示例使用索引合并提示,但其他索引级提示遵循关于提示忽略和优化器提示相对于 optimizer_switch 系统变量或索引提示的优先级的相同原则。

假设表 t1 包含列 abcd;并且分别在 abc 上存在名为 i_ai_bi_c 的索引。

SELECT /*+ INDEX_MERGE(t1 i_a, i_b, i_c)*/ * FROM t1
  WHERE a = 1 AND b = 2 AND c = 3 AND d = 4;

在这种情况下,索引合并用于 (i_a, i_b, i_c)

SELECT /*+ INDEX_MERGE(t1 i_a, i_b, i_c)*/ * FROM t1
  WHERE b = 1 AND c = 2 AND d = 3;

在这种情况下,索引合并用于 (i_b, i_c)

/*+ INDEX_MERGE(t1 i_a, i_b) NO_INDEX_MERGE(t1 i_b) */

NO_INDEX_MERGE 被忽略,因为存在针对同一表的先前提示。

/*+ NO_INDEX_MERGE(t1 i_a, i_b) INDEX_MERGE(t1 i_b) */

INDEX_MERGE 被忽略,因为存在针对同一表的先前提示。

对于 INDEX_MERGENO_INDEX_MERGE 优化器提示,这些优先级规则适用。

  • 如果指定了优化器提示并且适用,它将优先于 optimizer_switch 系统变量的与索引合并相关的标志。

    SET optimizer_switch='index_merge_intersection=off';
    SELECT /*+ INDEX_MERGE(t1 i_b, i_c) */ * FROM t1
    WHERE b = 1 AND c = 2 AND d = 3;

    提示优先于 optimizer_switch。在这种情况下,索引合并用于 (i_b, i_c)

    SET optimizer_switch='index_merge_intersection=on';
    SELECT /*+ INDEX_MERGE(t1 i_b) */ * FROM t1
    WHERE b = 1 AND c = 2 AND d = 3;

    提示只指定一个索引,因此不适用,并且 optimizer_switch 标志 (on) 适用。如果优化器评估它成本效益高,则使用索引合并。

    SET optimizer_switch='index_merge_intersection=off';
    SELECT /*+ INDEX_MERGE(t1 i_b) */ * FROM t1
    WHERE b = 1 AND c = 2 AND d = 3;

    提示只指定一个索引,因此不适用,并且 optimizer_switch 标志 (off) 适用。不使用索引合并。

  • 索引级别优化器提示 GROUP_INDEXINDEXJOIN_INDEXORDER_INDEX 优先于等效的 FORCE INDEX 提示;也就是说,它们会导致忽略 FORCE INDEX 提示。类似地,NO_GROUP_INDEXNO_INDEXNO_JOIN_INDEXNO_ORDER_INDEX 提示优先于任何 IGNORE INDEX 等效项,也会导致它们被忽略。

    索引级别优化器提示 GROUP_INDEXNO_GROUP_INDEXINDEXNO_INDEXJOIN_INDEXNO_JOIN_INDEXORDER_INDEXNO_ORDER_INDEX 提示优先于所有其他优化器提示,包括其他索引级别优化器提示。任何其他优化器提示仅应用于这些提示允许的索引。

    提示 GROUP_INDEXINDEXJOIN_INDEXORDER_INDEX 等效于 FORCE INDEX 而不是 USE INDEX。这是因为使用一个或多个这些提示意味着仅当无法使用命名索引之一在表中查找行时才使用表扫描。为了使 MySQL 使用与给定 USE INDEX 实例相同的索引或索引集,可以使用 NO_INDEXNO_JOIN_INDEXNO_GROUP_INDEXNO_ORDER_INDEX 或这些提示的组合。

    要复制 USE INDEX 在查询 SELECT a,c FROM t1 USE INDEX FOR ORDER BY (i_a) ORDER BY a 中的作用,可以使用 NO_ORDER_INDEX 优化器提示来覆盖表上的所有索引,除了所需的索引,如下所示。

    SELECT /*+ NO_ORDER_INDEX(t1 i_b,i_c) */ a,c
        FROM t1
        ORDER BY a;

    尝试将针对整个表的 NO_ORDER_INDEXUSE INDEX FOR ORDER BY 结合使用无法实现此目的,因为 NO_ORDER_BY 会导致忽略 USE INDEX,如下所示。

    mysql> EXPLAIN SELECT /*+ NO_ORDER_INDEX(t1) */ a,c FROM t1
        ->     USE INDEX FOR ORDER BY (i_a) ORDER BY a\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: t1
       partitions: NULL
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 256
         filtered: 100.00
            Extra: Using filesort
  • 索引提示 USE INDEXFORCE INDEXIGNORE INDEX 的优先级高于 INDEX_MERGENO_INDEX_MERGE 优化器提示。

    /*+ INDEX_MERGE(t1 i_a, i_b, i_c) */ ... IGNORE INDEX i_a

    IGNORE INDEX 优先于 INDEX_MERGE,因此索引 i_a 被排除在索引合并的可能范围之外。

    /*+ NO_INDEX_MERGE(t1 i_a, i_b) */ ... FORCE INDEX i_a, i_b

    由于 FORCE INDEX,索引合并对于 i_a, i_b 被禁止,但优化器被迫使用 i_ai_b 用于 rangeref 访问。没有冲突;两个提示都适用。

  • 如果 IGNORE INDEX 提示命名多个索引,则这些索引不可用于索引合并。

  • 提示 FORCE INDEXUSE INDEX 只使命名的索引可用于索引合并。

    SELECT /*+ INDEX_MERGE(t1 i_a, i_b, i_c) */ a FROM t1
    FORCE INDEX (i_a, i_b) WHERE c = 'h' AND a = 2 AND b = 'b';

    索引合并交集访问算法用于 (i_a, i_b)。如果将 FORCE INDEX 更改为 USE INDEX,也是如此。

子查询优化器提示

子查询提示会影响是否使用半连接转换以及允许哪些半连接策略,以及在不使用半连接时,是否使用子查询物化或 INEXISTS 转换。有关这些优化的更多信息,请参见 第 10.2.2 节,“优化子查询、派生表、视图引用和公用表表达式”

影响半连接策略的提示的语法

hint_name([@query_block_name] [strategy [, strategy] ...])

语法指的是以下术语

  • hint_name: 允许使用以下提示名称

  • strategy:要启用或禁用的半连接策略。允许使用以下策略名称:DUPSWEEDOUTFIRSTMATCHLOOSESCANMATERIALIZATION

    对于 SEMIJOIN 提示,如果未命名任何策略,则根据 optimizer_switch 系统变量启用策略,如果可能,则使用半连接。如果命名策略但不适用于语句,则使用 DUPSWEEDOUT

    对于 NO_SEMIJOIN 提示,如果未命名任何策略,则不使用半连接。如果命名的策略排除了适用于语句的所有策略,则使用 DUPSWEEDOUT

如果一个子查询嵌套在另一个子查询中,并且两者都合并到外部查询的半连接中,则忽略对最内层查询的半连接策略的任何指定。仍然可以使用 SEMIJOINNO_SEMIJOIN 提示来启用或禁用此类嵌套子查询的半连接转换。

如果禁用 DUPSWEEDOUT,优化器有时可能会生成一个远非最佳的查询计划。这是由于贪婪搜索期间的启发式修剪造成的,这可以通过设置 optimizer_prune_level=0 来避免。

示例

SELECT /*+ NO_SEMIJOIN(@subq1 FIRSTMATCH, LOOSESCAN) */ * FROM t2
  WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3);
SELECT /*+ SEMIJOIN(@subq1 MATERIALIZATION, DUPSWEEDOUT) */ * FROM t2
  WHERE t2.a IN (SELECT /*+ QB_NAME(subq1) */ a FROM t3);

影响是否使用子查询物化或 INEXISTS 转换的提示的语法

SUBQUERY([@query_block_name] strategy)

提示名称始终为 SUBQUERY

对于 SUBQUERY 提示,允许使用以下 strategy 值:INTOEXISTSMATERIALIZATION

示例

SELECT id, a IN (SELECT /*+ SUBQUERY(MATERIALIZATION) */ a FROM t1) FROM t2;
SELECT * FROM t2 WHERE t2.a IN (SELECT /*+ SUBQUERY(INTOEXISTS) */ a FROM t1);

对于半连接和 SUBQUERY 提示,前面的 @query_block_name 指定提示适用的查询块。如果提示不包含前面的 @query_block_name,则提示适用于它所在的查询块。要为查询块分配名称,请参见 用于命名查询块的优化器提示

如果提示注释包含多个子查询提示,则使用第一个。如果有其他后续的相同类型的提示,则会产生警告。其他类型的后续提示将被静默忽略。

语句执行时间优化器提示

提示 MAX_EXECUTION_TIME 仅适用于 SELECT 语句。它为语句执行时间设置一个限制 N(以毫秒为单位的超时值),在该时间限制之前,服务器将终止语句执行。

MAX_EXECUTION_TIME(N)

具有 1 秒(1000 毫秒)超时的示例

SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM t1 INNER JOIN t2 WHERE ...

提示 MAX_EXECUTION_TIME(N) 设置语句执行超时时间为 N 毫秒。如果此选项缺失或 N 为 0,则适用由 max_execution_time 系统变量确定的语句超时时间。

提示 MAX_EXECUTION_TIME 如下所示。

  • 对于具有多个 SELECT 关键字的语句(例如联合或具有子查询的语句),MAX_EXECUTION_TIME 应用于整个语句,并且必须出现在第一个 SELECT 之后。

  • 它适用于只读 SELECT 语句。非只读语句是调用修改数据的存储函数作为副作用的语句。

  • 它不适用于存储程序中的 SELECT 语句,并且会被忽略。

变量设置提示语法

提示 SET_VAR 临时设置系统变量的会话值(对于单个语句的持续时间)。示例

SELECT /*+ SET_VAR(sort_buffer_size = 16M) */ name FROM people ORDER BY name;
INSERT /*+ SET_VAR(foreign_key_checks=OFF) */ INTO t2 VALUES(2);
SELECT /*+ SET_VAR(optimizer_switch = 'mrr_cost_based=off') */ 1;

提示 SET_VAR 的语法

SET_VAR(var_name = value)

var_name 命名具有会话值的系统变量(尽管并非所有此类变量都可以命名,如稍后所述)。value 是要分配给变量的值;该值必须是标量。

SET_VAR 会进行临时变量更改,如以下语句所示。

mysql> SELECT @@unique_checks;
+-----------------+
| @@unique_checks |
+-----------------+
|               1 |
+-----------------+
mysql> SELECT /*+ SET_VAR(unique_checks=OFF) */ @@unique_checks;
+-----------------+
| @@unique_checks |
+-----------------+
|               0 |
+-----------------+
mysql> SELECT @@unique_checks;
+-----------------+
| @@unique_checks |
+-----------------+
|               1 |
+-----------------+

使用 SET_VAR,无需保存和恢复变量值。这使您可以用单个语句替换多个语句。考虑以下语句序列。

SET @saved_val = @@SESSION.var_name;
SET @@SESSION.var_name = value;
SELECT ...
SET @@SESSION.var_name = @saved_val;

该序列可以用以下单个语句替换。

SELECT /*+ SET_VAR(var_name = value) ...

独立 SET 语句允许使用以下任何语法来命名会话变量。

SET SESSION var_name = value;
SET @@SESSION.var_name = value;
SET @@.var_name = value;

因为 SET_VAR 提示仅适用于会话变量,会话范围是隐式的,并且 SESSION@@SESSION.@@ 既不需要也不允许。包含显式会话指示器语法会导致 SET_VAR 提示被忽略并发出警告。

并非所有会话变量都允许与 SET_VAR 一起使用。各个系统变量描述指示每个变量是否可提示;请参阅 第 7.1.8 节,“服务器系统变量”。您也可以在运行时通过尝试使用 SET_VAR 检查系统变量。如果变量不可提示,则会发出警告。

mysql> SELECT /*+ SET_VAR(collation_server = 'utf8mb4') */ 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Warning
   Code: 4537
Message: Variable 'collation_server' cannot be set using SET_VAR hint.

SET_VAR 语法只允许设置单个变量,但可以给出多个提示来设置多个变量。

SELECT /*+ SET_VAR(optimizer_switch = 'mrr_cost_based=off')
           SET_VAR(max_heap_table_size = 1G) */ 1;

如果在同一语句中出现多个具有相同变量名的提示,则应用第一个提示,而其他提示将被忽略并发出警告。

SELECT /*+ SET_VAR(max_heap_table_size = 1G)
           SET_VAR(max_heap_table_size = 3G) */ 1;

在这种情况下,第二个提示将被忽略,并发出关于其冲突的警告。

如果未找到指定名称的系统变量,或变量值不正确,则 SET_VAR 提示将被忽略并发出警告。

SELECT /*+ SET_VAR(max_size = 1G) */ 1;
SELECT /*+ SET_VAR(optimizer_switch = 'mrr_cost_based=yes') */ 1;

对于第一个语句,不存在 max_size 变量。对于第二个语句,mrr_cost_based 接受 onoff 值,因此尝试将其设置为 yes 是不正确的。在这两种情况下,提示都会被忽略并发出警告。

SET_VAR 提示仅允许在语句级别使用。如果在子查询中使用,则提示将被忽略并发出警告。

副本在复制的语句中忽略 SET_VAR 提示,以避免潜在的安全问题。

资源组提示语法

RESOURCE_GROUP 优化器提示用于资源组管理(请参阅 第 7.1.16 节,“资源组”)。此提示将临时(在语句执行期间)将执行语句的线程分配给指定的资源组。它需要 RESOURCE_GROUP_ADMINRESOURCE_GROUP_USER 权限。

示例

SELECT /*+ RESOURCE_GROUP(USR_default) */ name FROM people ORDER BY name;
INSERT /*+ RESOURCE_GROUP(Batch) */ INTO t2 VALUES(2);

RESOURCE_GROUP 提示的语法

RESOURCE_GROUP(group_name)

group_name 指示在语句执行期间应将线程分配到的资源组。如果组不存在,则会发出警告,并且会忽略提示。

RESOURCE_GROUP 提示必须出现在初始语句关键字(SELECTINSERTREPLACEUPDATEDELETE)之后。

RESOURCE_GROUP 的替代方案是 SET RESOURCE GROUP 语句,它非临时地将线程分配到资源组。请参阅 第 15.7.2.4 节,“SET RESOURCE GROUP 语句”

用于命名查询块的优化器提示

表级、索引级和子查询优化器提示允许将特定的查询块命名为其参数语法的一部分。要创建这些名称,请使用 QB_NAME 提示,它将名称分配给出现该提示的查询块。

QB_NAME(name)

QB_NAME 提示可用于以清晰的方式明确说明其他提示应用于哪些查询块。它们还允许所有非查询块名称提示在单个提示注释内指定,以方便理解复杂的语句。请考虑以下语句

SELECT ...
  FROM (SELECT ...
  FROM (SELECT ... FROM ...)) ...

QB_NAME 提示将名称分配给语句中的查询块

SELECT /*+ QB_NAME(qb1) */ ...
  FROM (SELECT /*+ QB_NAME(qb2) */ ...
  FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...

然后其他提示可以使用这些名称来引用相应的查询块

SELECT /*+ QB_NAME(qb1) MRR(@qb1 t1) BKA(@qb2) NO_MRR(@qb3t1 idx1, id2) */ ...
  FROM (SELECT /*+ QB_NAME(qb2) */ ...
  FROM (SELECT /*+ QB_NAME(qb3) */ ... FROM ...)) ...

最终效果如下

查询块名称是标识符,遵循有关哪些名称有效以及如何引用它们的常用规则(请参阅 第 11.2 节,“模式对象名称”)。例如,包含空格的查询块名称必须用引号括起来,可以使用反引号来完成

SELECT /*+ BKA(@`my hint name`) */ ...
  FROM (SELECT /*+ QB_NAME(`my hint name`) */ ...) ...

如果启用了 ANSI_QUOTES SQL 模式,则也可以在双引号内引用查询块名称。

SELECT /*+ BKA(@"my hint name") */ ...
  FROM (SELECT /*+ QB_NAME("my hint name") */ ...) ...