一种控制优化器策略的方法是设置optimizer_switch
系统变量(见第 10.9.2 节,“可切换优化”)。对该变量的更改将影响所有后续查询的执行;要使一个查询不同于另一个查询,需要在每个查询之前更改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 可用的优化器提示
Hint Name | Description | Applicable Scopes |
---|---|---|
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.3 中无效) | 查询块、表 |
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-to-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
查看优化器提示如何影响执行计划。使用SHOW WARNINGS
立即在EXPLAIN
之后查看提示如何使用。扩展的EXPLAIN
输出显示在随后的SHOW WARNINGS
中,指示哪些提示被使用。忽略的提示不显示。
一个提示注释可以包含多个提示,但查询块不能包含多个提示注释。这是有效的:
SELECT /*+ BNL(t1) BKA(t2) */ ...
但这无效:
SELECT /*+ BNL(t1) */ /* BKA(t2) */ ...
当提示注释包含多个提示时,存在重复和冲突的可能性。以下是一般指南。对于特定类型的提示,可能会应用其他规则,如提示描述中所示。
-
重复提示:对于类似于
/*+ MRR(idx1) MRR(idx1) */
的提示,MySQL 使用第一个提示并发出关于重复提示的警告。 -
冲突提示:对于类似于
/*+ MRR(idx1) NO_MRR(idx1) */
的提示,MySQL 使用第一个提示并发出关于第二个冲突提示的警告。
查询块名称是标识符,遵循通常的名称规则(见 第 11.2 节,“Schema 对象名称”)。
提示名称、查询块名称和策略名称不区分大小写。表和索引名称的引用遵循通常的标识符大小写敏感规则(见 第 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
提示不命名表,适用于查询块中的所有表。如果表有别名,提示必须引用别名,而不是表名称。
提示中的表名称不能以模式名称限定。
-
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
。该JOIN_PREFIX
提示被忽略,因为表t1
不能依赖于t2
。忽略的提示不显示在扩展的EXPLAIN
输出中。 -
与
const
表的交互MySQL 优化器将
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 节,“块嵌套循环和批量键访问连接”)。
-
是否将派生表、视图引用或公共表表达式合并到外部查询块中,或者使用内部临时表进行materialization。
-
派生条件推送优化的使用。见 第 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.3 中无效;请改用BNL
或NO_BNL
。 -
MERGE
,NO_MERGE
: 启用或禁用指定表、视图引用或公共表表达式的合并;或禁用合并并使用物化代替。
Note要使用块嵌套循环或批量键访问提示启用连接缓冲区,必须为外连接的所有内部表启用连接缓冲区。
-
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
应用块嵌套循环连接,缓冲来自 t1
的行,然后开始从 t2
读取。如果优化器选择首先处理 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
操作。等同于索引提示FORCE INDEX FOR GROUP BY
,IGNORE INDEX FOR GROUP BY
。 -
INDEX
,NO_INDEX
: 作为JOIN_INDEX
,GROUP_INDEX
和ORDER_INDEX
的组合,强制服务器使用指定索引或索引的任何和所有范围,或者作为NO_JOIN_INDEX
,NO_GROUP_INDEX
和NO_ORDER_INDEX
的组合,导致服务器忽略指定索引或索引的任何和所有范围。等同于FORCE INDEX
,IGNORE 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
等。等同于FORCE INDEX FOR JOIN
,IGNORE INDEX FOR JOIN
。 -
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使用或忽略指定索引或索引以对行进行排序。等同于FORCE INDEX FOR ORDER BY
,IGNORE INDEX FOR ORDER BY
。 -
SKIP_SCAN
,NO_SKIP_SCAN
:启用或禁用指定表或索引的Skip Scan访问方法。有关此访问方法的信息,请参阅Skip Scan范围访问方法。该
SKIP_SCAN
提示强制优化器使用Skip Scan对指定表使用指定的索引集。如果没有指定索引,优化器将考虑所有可能的索引并选择最便宜的那个。该提示可能会被忽略,如果索引不可应用于给定的语句。该
NO_SKIP_SCAN
提示禁用Skip Scan对指定的索引。如果该提示没有指定索引,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;
以下示例使用Index Merge提示,但其他索引级提示遵循相同的原则,忽略提示和优化器提示在optimizer_switch
系统变量或索引提示方面的优先级。
假设表t1
具有列a
、b
、c
和d
;并且存在索引i_a
、i_b
和i_c
在a
、b
和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;
在这种情况下,Index Merge用于(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;
在这种情况下,Index Merge用于(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
系统变量的 Index Merge 相关标志。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
。在这种情况下,Index Merge 用于(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
) 应用。如果优化器认为它是成本高效的,则使用 Index Merge。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
) 应用。Index Merge 不会被使用。 -
索引级优化器提示
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
,Index Merge 被禁止用于i_a, i_b
,但优化器被迫使用i_a
或i_b
进行range
或ref
访问。没有冲突;两个提示都是可用的。 -
如果
IGNORE INDEX
提示命名了多个索引,那些索引将不可用于 Index Merge。 -
使用
FORCE INDEX
和USE INDEX
提示只能使命名的索引可用于 Index Merge。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';
Index Merge 交集访问算法用于
(i_a, i_b)
。如果将FORCE INDEX
更改为USE INDEX
,也是如此。
子查询提示影响是否使用半连接变换和哪些半连接策略允许,并且,当半连接不使用时,是否使用子查询物化或 IN
-to-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
-to-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
提示,leading @
指定了应用于哪个查询块的提示。如果提示不包含 leading 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") */ ...) ...