控制优化器策略的一种方法是设置 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 9.0 中没有效果) | 查询块,表 |
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
。JOIN_PREFIX
提示被忽略,因为表t1
不能依赖于t2
。忽略的提示不会显示在扩展的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 9.0 中没有效果;请改用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
之前缓冲来自 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
提示强制优化器使用跳过扫描为指定的表使用指定的索引集。如果未指定索引,则优化器会考虑所有可能的索引并选择最便宜的组合。如果索引不适用于给定语句,则可能会忽略提示。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") */ ...) ...