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 可用优化器提示
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.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` 语句前面 prefaced by `
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 节“模式对象名称”)。
提示名称、查询块名称和策略名称是不区分大小写的。对表和索引名称的引用遵循通常的标识符大小写敏感性规则(见 第 11.2.3 节“标识符大小写敏感性”)。
Join-Order 优化器提示
Join-Order 提示影响优化器如何连接表的顺序。
JOIN_固定顺序提示语法:
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_固定顺序
:强制优化器按照FROM子句中表的出现顺序进行连接。这与指定SELECT STRAIGHT_JOIN
相同。 -
JOIN_ORDER
:告诉优化器按照指定的表顺序进行连接。提示适用于命名的表。优化器可能会将未在连接顺序中命名的表放在指定表之间。 -
JOIN_前缀
:告诉优化器按照指定的表顺序为连接执行计划中的第一个表进行连接。提示适用于命名的表。优化器将所有其他表放在命名表之后。 -
JOIN_后缀
:告诉优化器按照指定的表顺序为连接执行计划中的最后一个表进行连接。提示适用于命名的表。优化器将所有其他表放在命名表之前。
-
-
tbl_name
:查询中使用的表的名称。一旦给出表名的提示,它就适用于它所命名的所有表。JOIN_固定顺序
提示没有给出任何表,并且适用于FROM子句中出现的所有表,其中它发生。如果表有别名,提示必须引用别名,而不是表名称。
在提示中不能使用带有模式名称的表名称。
-
query_block_name
:提示适用的查询块。如果提示没有包含任何前缀@
,那么它适用于发生的查询块。对于query_block_name
语法,提示适用于命名表中的指定查询块。要为查询块分配名称,请参阅Optimizer Hints for Naming Query Blocks。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_前缀
和JOIN_后缀
提示。任何后续相同类型的提示都会被忽略,伴随着警告。JOIN_ORDER
可以多次指定。示例:
/*+ JOIN_PREFIX(t1) JOIN_PREFIX(t2) */
第二个
JOIN_前缀
提示会被忽略,伴随着警告。/*+ JOIN_PREFIX(t1) JOIN_SUFFIX(t2) */
两个提示都适用。没有警告发生。
/*+ JOIN_ORDER(t1, t2) JOIN_ORDER(t2, t3) */
两个提示都适用。没有警告发生。
-
冲突的提示
在某些情况下,提示可能会冲突,比如当
JOIN_ORDER
和JOIN_前缀
有无法同时应用的表顺序时: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)
-
与连接操作类型的交互
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
读取之前缓冲来自 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
:启用或禁用Index Merge访问方法对于指定的表或索引。关于此访问方法的信息,请参阅第10.2.1.3节,“索引合并优化”。这些提示适用于Index Merge的所有三个算法。索引合并提示
INDEX_MERGE
强制优化器使用Index Merge访问方法对指定表进行操作,使用指定的索引集。如果没有指定索引,优化器将考虑所有可能的索引组合,并选择成本最低的一个。这个提示可能会被忽略,如果给定的语句对于索引组合不适用。索引合并提示
NO_INDEX_MERGE
禁用Index Merge访问方法的任何组合,涉及到指定的索引。如果提示没有指定索引,那么对表进行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
:禁用索引范围访问对指定的表或索引。这也会禁用Index Merge和Loose Index Scan对表或索引。默认情况下,范围访问是一个候选优化策略,所以没有启用的提示。当范围可能很高,且进行范围优化需要大量资源时,这个提示可能会有用。
-
ORDER_INDEX
,NO_ORDER_INDEX
:强制或忽略排序时使用的指定索引或索引集。相当于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
语法,提示将应用于命名表中的指定查询块。为查询块命名,请参阅 Optimizer Hints for Naming Query Blocks。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
;并且存在名为 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;
索引合并用于 (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
)适用。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
被排除在Index Merge的可能范围之外。/*+ 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
提示命名了多个索引,那么这些索引对于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 intersection访问算法用于
(i_a, i_b)
。同样,如果将FORCE INDEX
更改为USE INDEX
,则相同的结果也适用。
子查询优化器提示
子查询提示影响是否使用半连接变换以及哪些半连接策略被允许,以及当不使用半连接时,是否使用子查询物化或IN
-to-EXISTS
变换。有关这些优化的更多信息,请参阅第10.2.2节,“优化子查询、派生表、视图引用和通用表达式”。
Syntax of hints that affect semijoin strategies:
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
提示,一个带有@
的前缀指定了提示适用的查询块。如果提示中没有包含query_block_name
@
,则提示应用于它出现的查询块。为查询块分配名称,请参阅Optimizer Hints for Naming Query Blocks.query_block_name
如果提示注释中包含多个子查询提示,第一个将被使用。其他跟随的相同类型的提示会产生警告。跟随的不同类型的提示将被忽略。
语句执行时间优化器提示
优化器提示的语法,用于影响半连接策略:
MAX_EXECUTION_TIME(N)
Example with a timeout of 1 second (1000 milliseconds):
SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM t1 INNER JOIN t2 WHERE ...
The MAX_EXECUTION_TIME(
hint sets a statement execution timeout of N
)N
milliseconds. If this option is absent or N
is 0, the statement timeout established by the max_execution_time
system variable applies.
以下是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(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.
多个提示可以给出以设置多个变量:
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
表示要为执行语句期间的线程分配到的资源组。如果该组不存在,将发出警告并忽略提示。
资源组优化器提示必须出现在初始语句关键字(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)
适用于表t1中的查询块qb1。 -
NO_ MRR(@qb3 t1 idx1, id2)
适用于表t1中的索引idx1和idx2在查询块qb3中。
查询块名称是标识符,遵循通常的规则来确定哪些名称有效以及如何引用它们(见第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") */ ...) ...