控制优化器策略的一种方法是设置 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 可用的优化器提示
提示名称 | 描述 | 适用范围 |
---|---|---|
BKA ,NO_BKA |
影响批处理键访问连接处理 | 查询块,表 |
BNL ,NO_BNL |
影响哈希连接优化 | 查询块,表 |
DERIVED_CONDITION_PUSHDOWN ,NO_DERIVED_CONDITION_PUSHDOWN |
对物化派生表使用或忽略派生条件下推优化 | 查询块,表 |
GROUP_INDEX ,NO_GROUP_INDEX |
在 GROUP BY 操作中使用或忽略指定的索引或索引进行索引扫描 |
索引 |
HASH_JOIN ,NO_HASH_JOIN |
影响哈希连接优化(在 MySQL 8.4 中没有影响) | 查询块,表 |
INDEX ,NO_INDEX |
作为 JOIN_INDEX 、GROUP_INDEX 和 ORDER_INDEX 的组合,或作为 NO_JOIN_INDEX 、NO_GROUP_INDEX 和 NO_ORDER_INDEX 的组合 |
索引 |
INDEX_MERGE 、NO_INDEX_MERGE |
影响索引合并优化 | 表、索引 |
JOIN_FIXED_ORDER |
使用 FROM 子句中指定的表顺序进行联接顺序 |
查询块 |
JOIN_INDEX 、NO_JOIN_INDEX |
使用或忽略指定的索引或索引用于任何访问方法 | 索引 |
JOIN_ORDER |
使用提示中指定的表顺序进行联接顺序 | 查询块 |
JOIN_PREFIX |
使用提示中指定的表顺序进行联接顺序的第一个表 | 查询块 |
JOIN_SUFFIX |
使用提示中指定的表顺序进行联接顺序的最后一个表 | 查询块 |
MAX_EXECUTION_TIME |
限制语句执行时间 | 全局 |
MERGE 、NO_MERGE |
影响派生表/视图合并到外部查询块中 | 表 |
MRR 、NO_MRR |
影响多范围读取优化 | 表、索引 |
NO_ICP |
影响索引条件下推优化 | 表、索引 |
NO_RANGE_OPTIMIZATION |
影响范围优化 | 表、索引 |
ORDER_INDEX 、NO_ORDER_INDEX |
使用或忽略指定的索引或索引用于排序行 | 索引 |
QB_NAME |
为查询块分配名称 | 查询块 |
RESOURCE_GROUP |
在语句执行期间设置资源组 | 全局 |
SEMIJOIN 、NO_SEMIJOIN |
影响半联接和反联接策略 | 查询块 |
SKIP_SCAN 、NO_SKIP_SCAN |
影响跳过扫描优化 | 表、索引 |
SET_VAR |
在语句执行期间设置变量 | 全局 |
SUBQUERY |
影响物化、IN 到 EXISTS 子查询策略 |
查询块 |
禁用优化会阻止优化器使用它。启用优化意味着优化器可以自由使用该策略,如果它适用于语句执行,而不是优化器一定会使用它。
MySQL 支持 SQL 语句中的注释,如 第 11.7 节,“注释” 中所述。优化器提示必须在 /*+ ... */
注释中指定。也就是说,优化器提示使用 /* ... */
C 风格注释语法的变体,在 /*
注释开头序列后加一个 +
字符。示例
/*+ BKA(t1) */
/*+ BNL(t1, t2) */
/*+ NO_RANGE_OPTIMIZATION(t4 PRIMARY) */
/*+ QB_NAME(qb2) */
在 +
字符后允许使用空格。
解析器在 SELECT
、UPDATE
、INSERT
、REPLACE
和 DELETE
语句的初始关键字之后识别优化器提示注释。在以下情况下允许使用提示
在查询和数据更改语句的开头
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);
提示控制合并到外部查询块的半联接表的行为。如果子查询 subq1
和 subq2
被转换为半联接,则表 t4@subq1
和 t5@subq2
将被合并到外部查询块。在这种情况下,在外部查询块中指定的提示控制 t4@subq1
、t5@subq2
表的行为。
优化器根据以下原则解析联接顺序提示
多个提示实例
仅应用每种类型的
JOIN_PREFIX
和JOIN_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_ORDER
和JOIN_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 支持多种类型的联接:
LEFT
、RIGHT
、INNER
、CROSS
、STRAIGHT_JOIN
。与指定的联接类型冲突的提示将被忽略,不会发出警告。示例
SELECT /*+ JOIN_PREFIX(t1, t2) */FROM t2 LEFT JOIN t1;
这里,提示中请求的联接顺序与
LEFT JOIN
所需的顺序之间存在冲突。提示将被忽略,不会发出警告。
表级提示会影响
块嵌套循环 (BNL) 和批处理键访问 (BKA) 联接处理算法的使用(参见 第 10.2.1.12 节,“块嵌套循环和批处理键访问联接”)。
派生表、视图引用或公用表表达式是否应合并到外部查询块中,还是使用内部临时表进行物化。
派生表条件下推优化的使用。参见 第 10.2.2.5 节,“派生表条件下推优化”。
这些提示类型适用于特定表或查询块中的所有表。
表级提示的语法
hint_name([@query_block_name] [tbl_name [, tbl_name] ...])
hint_name([tbl_name@query_block_name [, tbl_name@query_block_name] ...])
语法指的是以下术语
hint_name
: 允许使用以下提示名称DERIVED_CONDITION_PUSHDOWN
、NO_DERIVED_CONDITION_PUSHDOWN
:为指定的表启用或禁用派生表条件下推的使用。有关更多信息,请参见 第 10.2.2.5 节,“派生表条件下推优化”。HASH_JOIN
、NO_HASH_JOIN
:这些提示在 MySQL 8.4 中没有效果;请改用BNL
或NO_BNL
。MERGE
、NO_MERGE
:为指定的表、视图引用或公用表表达式启用合并;或禁用合并并改用物化。
注意若要使用块嵌套循环或批处理键访问提示为外联接的任何内部表启用联接缓冲,则必须为外联接的所有内部表启用联接缓冲。
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
是一个发送表。
对于 MERGE
和 NO_MERGE
提示,以下优先级规则适用
提示优先于任何不是技术约束的优化器启发式算法。(如果提供提示作为建议没有效果,则优化器有理由忽略它。)
提示优先于
derived_merge
的optimizer_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_INDEX
、NO_GROUP_INDEX
:为GROUP BY
操作的索引扫描启用或禁用指定的索引。INDEX
、NO_INDEX
:充当JOIN_INDEX
、GROUP_INDEX
和ORDER_INDEX
的组合,强制服务器对任何和所有范围使用指定的索引,或充当NO_JOIN_INDEX
、NO_GROUP_INDEX
和NO_ORDER_INDEX
的组合,这会导致服务器忽略任何和所有范围内的指定索引。INDEX_MERGE
、NO_INDEX_MERGE
:为指定的表或索引启用或禁用索引合并访问方法。有关此访问方法的信息,请参见 第 10.2.1.3 节,“索引合并优化”。这些提示适用于所有三种索引合并算法。INDEX_MERGE
提示强制优化器使用索引合并来使用指定的索引集访问指定的表。如果未指定索引,优化器将考虑所有可能的索引组合并选择最便宜的组合。如果索引组合不适用于给定的语句,则可能会忽略该提示。NO_INDEX_MERGE
提示会禁用涉及任何指定索引的索引合并组合。如果该提示未指定任何索引,则不允许对该表进行索引合并。JOIN_INDEX
、NO_JOIN_INDEX
:强制 MySQL 对任何访问方法(例如ref
、range
、index_merge
等)使用或忽略指定的索引。MRR
、NO_MRR
:为指定的表或索引启用或禁用 MRR。MRR 提示仅适用于InnoDB
和MyISAM
表。有关此访问方法的信息,请参见 第 10.2.1.11 节,“多范围读取优化”。NO_ICP
:为指定的表或索引禁用 ICP。默认情况下,ICP 是一个候选优化策略,因此没有启用它的提示。有关此访问方法的信息,请参见 第 10.2.1.6 节,“索引条件下推优化”。NO_RANGE_OPTIMIZATION
:为指定的表或索引禁用索引范围访问。此提示还会禁用该表或索引的索引合并和松散索引扫描。默认情况下,范围访问是一个候选优化策略,因此没有启用它的提示。当范围数可能很多且范围优化需要大量资源时,此提示可能很有用。
ORDER_INDEX
、NO_ORDER_INDEX
:导致 MySQL 对行进行排序时使用或忽略指定的索引。SKIP_SCAN
、NO_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
包含列 a
、b
、c
和 d
;并且分别在 a
、b
和 c
上存在名为 i_a
、i_b
和 i_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_MERGE
和 NO_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_INDEX
、INDEX
、JOIN_INDEX
和ORDER_INDEX
优先于等效的FORCE INDEX
提示;也就是说,它们会导致忽略FORCE INDEX
提示。类似地,NO_GROUP_INDEX
、NO_INDEX
、NO_JOIN_INDEX
和NO_ORDER_INDEX
提示优先于任何IGNORE INDEX
等效项,也会导致它们被忽略。索引级别优化器提示
GROUP_INDEX
、NO_GROUP_INDEX
、INDEX
、NO_INDEX
、JOIN_INDEX
、NO_JOIN_INDEX
、ORDER_INDEX
和NO_ORDER_INDEX
提示优先于所有其他优化器提示,包括其他索引级别优化器提示。任何其他优化器提示仅应用于这些提示允许的索引。提示
GROUP_INDEX
、INDEX
、JOIN_INDEX
和ORDER_INDEX
等效于FORCE INDEX
而不是USE INDEX
。这是因为使用一个或多个这些提示意味着仅当无法使用命名索引之一在表中查找行时才使用表扫描。为了使 MySQL 使用与给定USE INDEX
实例相同的索引或索引集,可以使用NO_INDEX
、NO_JOIN_INDEX
、NO_GROUP_INDEX
、NO_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_INDEX
与USE 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 INDEX
、FORCE INDEX
和IGNORE INDEX
的优先级高于INDEX_MERGE
和NO_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_a
或i_b
用于range
或ref
访问。没有冲突;两个提示都适用。如果
IGNORE INDEX
提示命名多个索引,则这些索引不可用于索引合并。提示
FORCE INDEX
和USE 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
,也是如此。
子查询提示会影响是否使用半连接转换以及允许哪些半连接策略,以及在不使用半连接时,是否使用子查询物化或 IN
到 EXISTS
转换。有关这些优化的更多信息,请参见 第 10.2.2 节,“优化子查询、派生表、视图引用和公用表表达式”。
影响半连接策略的提示的语法
hint_name([@query_block_name] [strategy [, strategy] ...])
语法指的是以下术语
hint_name
: 允许使用以下提示名称SEMIJOIN
、NO_SEMIJOIN
:启用或禁用命名的半连接策略。
strategy
:要启用或禁用的半连接策略。允许使用以下策略名称:DUPSWEEDOUT
、FIRSTMATCH
、LOOSESCAN
、MATERIALIZATION
。对于
SEMIJOIN
提示,如果未命名任何策略,则根据optimizer_switch
系统变量启用策略,如果可能,则使用半连接。如果命名策略但不适用于语句,则使用DUPSWEEDOUT
。对于
NO_SEMIJOIN
提示,如果未命名任何策略,则不使用半连接。如果命名的策略排除了适用于语句的所有策略,则使用DUPSWEEDOUT
。
如果一个子查询嵌套在另一个子查询中,并且两者都合并到外部查询的半连接中,则忽略对最内层查询的半连接策略的任何指定。仍然可以使用 SEMIJOIN
和 NO_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);
影响是否使用子查询物化或 IN
到 EXISTS
转换的提示的语法
SUBQUERY([@query_block_name] strategy)
提示名称始终为 SUBQUERY
。
对于 SUBQUERY
提示,允许使用以下 strategy
值:INTOEXISTS
、MATERIALIZATION
。
示例
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
接受 on
或 off
值,因此尝试将其设置为 yes
是不正确的。在这两种情况下,提示都会被忽略并发出警告。
SET_VAR
提示仅允许在语句级别使用。如果在子查询中使用,则提示将被忽略并发出警告。
副本在复制的语句中忽略 SET_VAR
提示,以避免潜在的安全问题。
RESOURCE_GROUP
优化器提示用于资源组管理(请参阅 第 7.1.16 节,“资源组”)。此提示将临时(在语句执行期间)将执行语句的线程分配给指定的资源组。它需要 RESOURCE_GROUP_ADMIN
或 RESOURCE_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
提示必须出现在初始语句关键字(SELECT
、INSERT
、REPLACE
、UPDATE
或 DELETE
)之后。
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 ...)) ...
最终效果如下
MRR(@qb1 t1)
应用于查询块qb1
中的表t1
。BKA(@qb2)
应用于查询块qb2
。NO_MRR(@qb3 t1 idx1, id2)
应用于查询块qb3
中表t1
的索引idx1
和idx2
。
查询块名称是标识符,遵循有关哪些名称有效以及如何引用它们的常用规则(请参阅 第 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") */ ...) ...